+
+**面试官:** 为什么不考研?
+
+**我:** 不喜欢学术氛围,巴拉巴拉。
+
+**面试官:** 你有什么问题吗?
+
+**我:** 我还有下一面吗。。。面试官说让我等,一周内答复。
+
+------
+
+等了十天,一度以为我凉了,内推人说我流程到 HR 了,让我等着吧可能 HR 太忙了,3.28 号 HR 打来了电话,当时在教室,我直接飞了出去。
+
+### HR 面
+
+**面试官:** 你好啊,先自我介绍下吧
+
+**我:** 巴拉巴拉....HR 面的技术面试和技术面的还是有所区别的!
+
+面试官人特别好,一听就是很会说话的小姐姐!说我这里给你悄悄透露下,你的评级是 A 哦!
+
+
+
+接下来就是几个经典 HR 面挂人的问题,什么难给我来什么,我看别人的 HR 面怎么都是聊聊天。。。
+
+**面试官:** 你为什么选择支付宝呢,你怎么看待支付宝?
+
+**我:** 我从个人情怀,公司理念,环境氛围,市场价值,趋势导向分析了一波(说白了就是疯狂夸支付宝,不过说实话我说的那些一点都没撒谎,阿里确实做到了。比如我举了个雷军和格力打赌 5 年 2000 亿销售额,大部分企业家关注的是利益,而马云更关注的是真的为人类为世界做一些事情,利益不是第一位的。)
+
+**面试官:** 明白了解,那你的优点我们都很明了了,你能说说你的缺点吗?
+
+> 缺点肯定不能是目标岗位需要的关键能力!!!
+>
+> 总之,记住一点,面试官问你这个问题的话,你可以说一些不影响你这个职位工作需要的一些缺点。比如你面试后端工程师,面试官问你的缺点是什么的话,你可以这样说:自己比较内向,平时不太爱与人交流,但是考虑到以后可能要和客户沟通,自己正在努力改。
+
+**我:** 据说这是 HR 面最难的一个问题。。。我当时翻了好几天的知乎才找到一个合适的,也符合我的答案:我有时候会表现的不太自信,比如阿里的内推二月份就开始了,其实我当时已经复习了很久了,但是老是觉得自己还不行,不敢投简历,于是又把书看了一遍才投的,当时也是舍友怂恿一波才投的,面了之后发现其实自己也没有很差。(划重点,一定要把自己的缺点圆回来)。
+
+**面试官:** HR 好像不太满意我的答案,继续问我还有缺点吗?
+
+**我:** 我说比较容易紧张吧,举了自己大一面实验室因为紧张没进去的例子,后来不断调整心态,现在已经好很多了。
+
+接下来又是个好难的问题。
+
+**面试官:** BAT 都给你 offer 了,你怎么选?
+
+其实我当时好想说,BT 是什么?不好意思我只知道阿里。
+
+**我 :** 哈哈哈哈开玩笑,就说了阿里的文化,支付宝给我们带来很多便利,想加入支付宝为人类做贡献!
+
+最后 HR 问了我实习时间,现在大几之类的问题,说肯定会给我发 offer 的,让我等着就好了,希望过两天能收到好的结果。
+
+
diff --git a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周(2018-8-7).md b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周(2018-8-7).md
deleted file mode 100644
index 4ca58dbf..00000000
--- a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第一周(2018-8-7).md
+++ /dev/null
@@ -1,253 +0,0 @@
-
-
-## 一 为什么 Java 中只有值传递?
-
-
-首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
-
-**Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
-
-**下面通过 3 个例子来给大家说明**
-
-### example 1
-
-
-```java
-public static void main(String[] args) {
- int num1 = 10;
- int num2 = 20;
-
- swap(num1, num2);
-
- System.out.println("num1 = " + num1);
- System.out.println("num2 = " + num2);
-}
-
-public static void swap(int a, int b) {
- int temp = a;
- a = b;
- b = temp;
-
- System.out.println("a = " + a);
- System.out.println("b = " + b);
-}
-```
-
-**结果:**
-
-```
-a = 20
-b = 10
-num1 = 10
-num2 = 20
-```
-
-**解析:**
-
-
-
-在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
-
-**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
-
-
-### example 2
-
-```java
- public static void main(String[] args) {
- int[] arr = { 1, 2, 3, 4, 5 };
- System.out.println(arr[0]);
- change(arr);
- System.out.println(arr[0]);
- }
-
- public static void change(int[] array) {
- // 将数组的第一个元素变为0
- array[0] = 0;
- }
-```
-
-**结果:**
-
-```
-1
-0
-```
-
-**解析:**
-
-
-
-array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
-
-
-**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
-
-**很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
-
-
-### example 3
-
-```java
-public class Test {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Student s1 = new Student("小张");
- Student s2 = new Student("小李");
- Test.swap(s1, s2);
- System.out.println("s1:" + s1.getName());
- System.out.println("s2:" + s2.getName());
- }
-
- public static void swap(Student x, Student y) {
- Student temp = x;
- x = y;
- y = temp;
- System.out.println("x:" + x.getName());
- System.out.println("y:" + y.getName());
- }
-}
-```
-
-**结果:**
-
-```
-x:小李
-y:小张
-s1:小张
-s2:小李
-```
-
-**解析:**
-
-交换之前:
-
-
-
-交换之后:
-
-
-
-
-通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
-
-### 总结
-
-Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
-值传递的。
-
-下面再总结一下Java中方法参数的使用情况:
-
-- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
-- 一个方法可以改变一个对象参数的状态。
-- 一个方法不能让对象参数引用一个新的对象。
-
-
-### 参考:
-
-《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节
-
-## 二 ==与equals(重要)
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
-- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
-
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
-- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
-
-
-
-## 三 hashCode与equals(重要)
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
-
-### hashCode()介绍
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
- /**
- * Returns a hash code value for the object. This method is
- * supported for the benefit of hash tables such as those provided by
- * {@link java.util.HashMap}.
- *
- * As much as is reasonably practical, the hashCode method defined by
- * class {@code Object} does return distinct integers for distinct
- * objects. (This is typically implemented by converting the internal
- * address of the object into an integer, but this implementation
- * technique is not required by the
- * Java™ programming language.)
- *
- * @return a hash code value for this object.
- * @see java.lang.Object#equals(java.lang.Object)
- * @see java.lang.System#identityHashCode
- */
- public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-### 为什么要有hashCode
-
-
-**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:**
-
-当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
-
-
-
-### hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-### 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
-
-我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-
-参考:
-
-[https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504)
-
-[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
-
-[https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html)
-
-[https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html)
-
diff --git a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md
deleted file mode 100644
index 426498cb..00000000
--- a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md
+++ /dev/null
@@ -1,200 +0,0 @@
-
-### String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?
-
-#### String和StringBuffer、StringBuilder的区别
-
-**可变性**
-
-
-简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
-
-StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
-
-AbstractStringBuilder.java
-
-```java
-abstract class AbstractStringBuilder implements Appendable, CharSequence {
- char[] value;
- int count;
- AbstractStringBuilder() {
- }
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
-```
-
-
-**线程安全性**
-
-String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
-
-**性能**
-
-每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-
-**对于三者使用的总结:**
-1. 操作少量的数据 = String
-2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
-3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer
-
-#### String为什么是不可变的吗?
-简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
-
-```java
- /** The value is used for character storage. */
- private final char value[];
-```
-
-#### String真的是不可变的吗?
-我觉得如果别人问这个问题的话,回答不可变就可以了。
-下面只是给大家看两个有代表性的例子:
-
-**1) String不可变但不代表引用不可以变**
-```java
- String str = "Hello";
- str = str + " World";
- System.out.println("str=" + str);
-```
-结果:
-```
-str=Hello World
-```
-解析:
-
-实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
-
-**2) 通过反射是可以修改所谓的“不可变”对象**
-
-```java
- // 创建字符串"Hello World", 并赋给引用s
- String s = "Hello World";
-
- System.out.println("s = " + s); // Hello World
-
- // 获取String类中的value字段
- Field valueFieldOfString = String.class.getDeclaredField("value");
-
- // 改变value属性的访问权限
- valueFieldOfString.setAccessible(true);
-
- // 获取s对象上的value属性的值
- char[] value = (char[]) valueFieldOfString.get(s);
-
- // 改变value所引用的数组中的第5个字符
- value[5] = '_';
-
- System.out.println("s = " + s); // Hello_World
-```
-
-结果:
-
-```
-s = Hello World
-s = Hello_World
-```
-
-解析:
-
-用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
-
-### 什么是反射机制?反射机制的应用场景有哪些?
-
-#### 反射机制介绍
-
-JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
-
-#### 静态编译和动态编译
-
-- **静态编译:**在编译时确定类型,绑定对象
-- **动态编译:**运行时确定类型,绑定对象
-
-#### 反射机制优缺点
-
-- **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
-- **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
-
-#### 反射的应用场景
-
-反射是框架设计的灵魂。
-
-在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
-
-举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中;
- 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
-
-**推荐阅读:**
-
-- [Reflection:Java反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral)
-- [Java基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078)
-### 什么是JDK?什么是JRE?什么是JVM?三者之间的联系与区别
-
-这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。
-
-**JDK:** 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。
-
-**JRE:** 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。
-
-**JVM:** 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。
-
-**区别与联系:**
-
- 1. JDK用于开发,JRE用于运行java程序 ;
- 2. JDK和JRE中都包含JVM ;
- 3. JVM是java编程语言的核心并且具有平台独立性。
-
-### 什么是字节码?采用字节码的最大好处是什么?
-
-**先看下java中的编译器和解释器:**
-
-Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为`.class`的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。
-
- Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
-
-**采用字节码的好处:**
-
-Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
-
-### Java和C++的区别
-
-我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!
-
-- 都是面向对象的语言,都支持封装、继承和多态
-- Java不提供指针来直接访问内存,程序内存更加安全
-- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
-- Java有自动内存管理机制,不需要程序员手动释放无用内存
-
-
-### 接口和抽象类的区别是什么?
-
-1. 接口的方法默认是public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
-2. 接口中的实例变量默认是final类型的,而抽象类中则不一定
-3. 一个类可以实现多个接口,但最多只能实现一个抽象类
-4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
-5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
-
-注意:Java8 后接口可以有默认实现( default )。
-
-### 成员变量与局部变量的区别有那些?
-
-1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰;
-2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存
-3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。
-
-### 重载和重写的区别
-
-**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
-
-**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。
-
-### 字符型常量和字符串常量的区别
-1) 形式上:
-字符常量是单引号引起的一个字符
-字符串常量是双引号引起的若干个字符
-2) 含义上:
-字符常量相当于一个整形值(ASCII值),可以参加表达式运算
-字符串常量代表一个地址值(该字符串在内存中存放位置)
-3) 占内存大小
-字符常量只占一个字节
-字符串常量占若干个字节(至少一个字符结束标志)
diff --git a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md b/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md
deleted file mode 100644
index 3cb02d73..00000000
--- a/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md
+++ /dev/null
@@ -1,195 +0,0 @@
-
-## 1. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?
-
-**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
-
-**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
-
-**线程** 是 **进程** 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
-
-**线程上下文的切换比进程上下文切换要快很多**
-
-- 进程切换时,涉及到当前进程的CPU环境的保存和新被调度运行进程的CPU环境的设置。
-- 线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。
-
-## 2. 线程有哪些基本状态?这些状态是如何定义的?
-
-1. **新建(new)**:新创建了一个线程对象。
-2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。
-3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
-4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种:
- - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waiting queue)中。
- - **(二). 同步阻塞**:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- - **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
-5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
-
-
-
-备注: 可以用早起坐地铁来比喻这个过程(下面参考自牛客网某位同学的回答):
-
-1. 还没起床:sleeping
-2. 起床收拾好了,随时可以坐地铁出发:Runnable
-3. 等地铁来:Waiting
-4. 地铁来了,但要排队上地铁:I/O阻塞
-5. 上了地铁,发现暂时没座位:synchronized阻塞
-6. 地铁上找到座位:Running
-7. 到达目的地:Dead
-
-
-## 3. 何为多线程?
-
-多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
-
-
-## 4. 为什么多线程是必要的?
-
-1. 使用线程可以把占据长时间的程序中的任务放到后台去处理。
-2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
-3. 程序的运行速度可能加快。
-
-## 5 使用多线程常见的三种方式
-
-### ①继承Thread类
-
-MyThread.java
-
-```java
-public class MyThread extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println("MyThread");
- }
-}
-```
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- MyThread mythread = new MyThread();
- mythread.start();
- System.out.println("运行结束");
- }
-
-}
-
-```
-运行结果:
-
-从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
-
-### ②实现Runnable接口
-推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。
-
-MyRunnable.java
-
-```java
-public class MyRunnable implements Runnable {
- @Override
- public void run() {
- System.out.println("MyRunnable");
- }
-}
-```
-
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- Runnable runnable=new MyRunnable();
- Thread thread=new Thread(runnable);
- thread.start();
- System.out.println("运行结束!");
- }
-
-}
-```
-运行结果:
-
-
-### ③使用线程池
-
-**在《阿里巴巴Java开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。**
-
-**为什么呢?**
-
-> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。**
-
-**另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险**
-
-> Executors 返回线程池对象的弊端如下:
->
-> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-对于线程池感兴趣的可以查看我的这篇文章:[《Java多线程学习(八)线程池与Executor 框架》](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484042&idx=1&sn=541dbf2cb969a151d79f4a4f837ee1bd&chksm=fd9854ebcaefddfd1876bb96ab218be3ae7b12546695a403075d4ed22e5e17ff30ebdabc8bbf#rd) 点击阅读原文即可查看到该文章的最新版。
-
-
-## 6 线程的优先级
-
-每个线程都具有各自的优先级,**线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态**。但这个并不意味着低
-优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。
-
-**线程优先级具有继承特性。** 比如A线程启动B线程,则B线程的优先级和A是一样的。
-
-**线程优先级具有随机性。** 也就是说线程优先级高的不一定每一次都先执行完。
-
-Thread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY(常数1)**,**Thread.NORM_PRIORITY(常数5)**,
-**Thread.MAX_PRIORITY(常数10)**。其中每个线程的优先级都在**Thread.MIN_PRIORITY(常数1)** 到**Thread.MAX_PRIORITY(常数10)** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY(常数5)**。
-
-学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。
-
-
-## 7 Java多线程分类
-
-### 用户线程
-
-运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
-
-### 守护线程
-
-运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。
-
-
-- **特点:** 一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作
-- **应用:** 数据库连接池中的检测线程,JVM虚拟机启动后的检测线程
-- **最常见的守护线程:** 垃圾回收线程
-
-
-**如何设置守护线程?**
-
-可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。
-
-注意事项:
-
- 1. setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
- 2. 在守护线程中产生的新线程也是守护线程
- 3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
-
-
-## 8 sleep()方法和wait()方法简单对比
-
-- 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁** 。
-- 两者都可以暂停线程的执行。
-- Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。
-- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒。
-
-
-## 9 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
-
-这是另一个非常经典的java多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
-
-new一个Thread,线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。
-start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。 而直接执行run()方法,会把run方法当成一个mian线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
-
-**总结: 调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里执行。**
-
-
-
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
index 4901f889..835b6a54 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
@@ -1,78 +1,89 @@
-最近浏览 Github ,收藏了一些还算不错的 Java面试/学习相关的仓库,分享给大家,希望对你有帮助。我暂且按照目前的 Star 数量来排序。
+昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章:[整理了一些有助于你拿Offer的文章]() 。今天分享一下最近逛Github看到了一些我觉得对于Java面试以及学习有帮助的仓库,这些仓库涉及Java核心知识点整理、Java常见面试题、算法、基础知识点比如网络和操作系统等等。
-本文由 SnailClimb 整理,如需转载请联系作者。
+## 知识点相关
-### 1. interviews
-
-- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)
-- star: 31k
-- 介绍: 软件工程技术面试个人指南。
-- 概览:
-
- 
-
-### 2. JCSprout
-
-- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
-- star: 17.7k
-- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。
-- 概览:
-
- 
-
-### 3. JavaGuide
+### 1.JavaGuide
- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- star: 17.4k
+- star: 64.0k
- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-- 概览:
- 
+### 2.CS-Notes
-### 4. technology-talk
+- Github 地址:
+- Star: 68.3k
+- 介绍: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
-- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)
-- star: 4.2k
-- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。
-
-### 5. fullstack-tutorial
-
-- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)
-- star: 2.8k
-- 介绍: Full Stack Developer Tutorial,后台技术栈/全栈开发/架构师之路,秋招/春招/校招/面试。 from zero to hero。
-- 概览:
-
- 
-
-### 6. java-bible
-
-- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)
-- star: 1.9k
-- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。
-- 概览:
-
- 
-
-### 7. EasyJob
-
-- Github地址:[https://github.com/it-interview/EasyJob](https://github.com/it-interview/EasyJob)
-- star: 1.9k
-- 介绍: 互联网求职面试题、知识点和面经整理。
-
-### 8. advanced-java
+### 3. advanced-java
- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- star: 1k
-- 介绍: 互联网 Java 工程师进阶知识完全扫盲
+- star: 23.4k
+- 介绍: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
-### 9. 3y
+### 4.JCSprout
+
+- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
+- star: 21.2k
+- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。
+
+### 5.toBeTopJavaer
+
+- Github地址:[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
+- star: 4.0 k
+- 介绍: Java工程师成神之路。
+
+### 6.architect-awesome
+
+- Github地址:[https://github.com/xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)
+- star: 34.4 k
+- 介绍:后端架构师技术图谱。
+
+### 7.technology-talk
+
+- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)
+- star: 6.1k
+- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。
+
+### 8.fullstack-tutorial
+
+- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)
+- star: 4.0k
+- 介绍: fullstack tutorial 2019,后台技术栈/架构师之路/全栈开发社区,春招/秋招/校招/面试。
+
+### 9.3y
- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)
-- star: 0.4 k
+- star: 1.9 k
- 介绍: Java 知识整合。
-除了这九个仓库,再推荐几个不错的学习方向的仓库给大家。
+### 10.java-bible
-1. Star 数高达 4w+的 CS 笔记-CS-Notes:[https://github.com/CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes)
-2. 后端(尤其是Java)程序员的 Linux 学习仓库-Linux-Tutorial:[https://github.com/judasn/Linux-Tutorial](https://github.com/judasn/Linux-Tutorial)( Star:4.6k)
-3. 两个算法相关的仓库,刷 Leetcode 的小伙伴必备:①awesome-java-leetcode:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode);②LintCode:[https://github.com/awangdev/LintCode](https://github.com/awangdev/LintCode)
+- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)
+- star: 2.3k
+- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。
+
+### 11.interviews
+
+- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)
+- star: 35.3k
+- 介绍: 软件工程技术面试个人指南(国外的一个项目,虽然有翻译版,但是不太推荐,因为很多内容并不适用于国内)。
+
+## 算法相关
+
+### 1.LeetCodeAnimation
+
+- Github 地址:
+- Star: 33.4k
+- 介绍: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
+
+### 2.awesome-java-leetcode
+
+- Github地址:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)
+- star: 6.1k
+- 介绍: LeetCode 上 Facebook 的面试题目。
+
+### 3.leetcode
+
+- Github地址:[https://github.com/azl397985856/leetcode](https://github.com/azl397985856/leetcode)
+- star: 12.0k
+- 介绍: LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
index ef111f4c..06d3ffc9 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
@@ -1,81 +1,101 @@
- 身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学那些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指名一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。
+身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习 Java?”、“Java 学习该学哪些东西?”、“我该如何准备 Java 面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往 Java 后端方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。
+
+
+
+- [Question1:我是双非/三本/专科学校的,我有机会进入大厂吗?](#question1我是双非三本专科学校的我有机会进入大厂吗)
+- [Question2:非计算机专业的学生能学好 Java 后台吗?我能进大厂吗?](#question2非计算机专业的学生能学好-java-后台吗我能进大厂吗)
+- [Question3: 我没有实习经历的话找工作是不是特别艰难?](#question3-我没有实习经历的话找工作是不是特别艰难)
+- [Question4: 我该如何准备面试呢?面试的注意事项有哪些呢?](#question4-我该如何准备面试呢面试的注意事项有哪些呢)
+- [Question5: 我该自学还是报培训班呢?](#question5-我该自学还是报培训班呢)
+- [Question6: 没有项目经历/博客/Github 开源项目怎么办?](#question6-没有项目经历博客github-开源项目怎么办)
+- [Question7: 大厂青睐什么样的人?](#question7-大厂青睐什么样的人)
+
+
### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗?
- 我自己也是非985非211学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。
+我自己也是非 985 非 211 学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。
- 首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。
+首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。
- 企业HR肯定是更喜欢高学历的人,毕竟985,211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢?
-
- 双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**
+企业 HR 肯定是更喜欢高学历的人,毕竟 985、211 优秀人才比例肯定比普通学校高很多,HR 团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢?
+双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入 BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如 ACM 中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**① 尽量在面试前最好有一个可以拿的出手的项目;② 有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③ 参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**
-### Question2:非计算机专业的学生能学好Java后台吗?我能进大厂吗?
+### Question2:非计算机专业的学生能学好 Java 后台吗?我能进大厂吗?
- 当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。
-
- 我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。
-
- 建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据机构与算法!一定学好数据机构与算法!一定学好数据机构与算法!”,重要的东西说3遍。
+当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面 90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考 Question1 的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。
+我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。
+建议非科班的同学,首先要打好计算机基础知识基础:① 计算机网络、② 操作系统、③ 数据机构与算法,我个人觉得这 3 个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据结构与算法!一定学好数据结构与算法!一定学好数据结构与算法!”,重要的东西说 3 遍。
### Question3: 我没有实习经历的话找工作是不是特别艰难?
- 没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。
+没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。
- 如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。
+如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。
### Question4: 我该如何准备面试呢?面试的注意事项有哪些呢?
-下面是我总结的一些准备面试的Tips以及面试必备的注意事项:
+下面是我总结的一些准备面试的 Tips 以及面试必备的注意事项:
-1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账);
+1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账);
2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。)
-3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。
-4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
+3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手 code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。
+4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
-6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
+6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:① 对项目整体设计的一个感受(面试官可能会让你画系统的架构图);② 在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④ 项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。
-
-**一些还算不错的 Java面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)
+**一些还算不错的 Java 面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下 Github 上开源的 Java 面试/学习相关的仓库,看完弄懂薪资至少增加 10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)
### Question5: 我该自学还是报培训班呢?
- 我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上8个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。
+我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上 8 个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。
- 另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。
+另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。
- 总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。
+总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。
-### Question6: 没有项目经历/博客/Github开源项目怎么办?
+### Question6: 没有项目经历/博客/Github 开源项目怎么办?
- 从现在开始做!
+从现在开始做!
- 网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。
+网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。
- 如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。
+如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN 等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。
- 多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目!
+多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目!
+### Question7: 大厂青睐什么样的人?
-### Question7: 大厂到底青睐什么样的应届生?
+**先从已经有两年左右开发经验的工程师角度来看:** 我们来看一下阿里官网支付宝 Java 高级开发工程师的招聘要求,从下面的招聘信息可以看出,除去 Java 基础/集合/多线程这些,这些能力格外重要:
- 从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求,我们大概可以总结归纳出下面这 4 点能给简历增加很多分数:
+1. **底层知识比如 jvm** :不只是懂理论更会实操;
+2. 面**向对象编程能力** :我理解这个不仅包括“面向对象编程”,还有 SOLID 软件设计原则,相关阅读:[《写了这么多年代码,你真的了解 SOLID 吗?》](https://insights.thoughtworks.cn/do-you-really-know-solid/)(我司大佬的一篇文章)
+3. **框架能力** :不只是使用那么简单,更要搞懂原理和机制!搞懂原理和机制的基础是要学会看源码。
+4. **分布式系统开发能力** :缓存、消息队列等等都要掌握,关键是还要能使用这些技术解决实际问题而不是纸上谈兵。
+5. **不错的 sense** :喜欢和尝试新技术、追求编写优雅的代码等等。
-- 参加过竞赛(含金量超高的是ACM);
-- 对数据结构与算法非常熟练;
-- 参与过实际项目(比如学校网站);
-- 参与过某个知名的开源项目或者自己的某个开源项目很不错;
+
- 除了我上面说的这三点,在面试Java工程师的时候,下面几点也提升你的个人竞争力:
+**再从应届生的角度来看:** 我们还是看阿里巴巴的官网相关应届生 Java 工程师招聘岗位的相关要求。
-- 熟悉Python、Shell、Perl等脚本语言;
-- 熟悉 Java 优化,JVM调优;
-- 熟悉 SOA 模式;
-- 熟悉自己所用框架的底层知识比如Spring;
-- 了解分布式一些常见的理论;
-- 具备高并发开发经验;大数据开发经验等等。
+
+结合阿里、腾讯等大厂招聘官网对于 Java 后端方向/后端方向的应届实习生的要求下面几点也提升你的个人竞争力:
+
+1. 参加过竞赛( 含金量超高的是 ACM );
+2. 对数据结构与算法非常熟练;
+3. 参与过实际项目(比如学校网站)
+4. 熟悉 Python、Shell、Perl 其中一门脚本语言;
+5. 熟悉如何优化 Java 代码、有写出质量更高的代码的意识;
+6. 熟悉 SOA 分布式相关的知识尤其是理论知识;
+7. 熟悉自己所用框架的底层知识比如 Spring;
+8. 有高并发开发经验;
+9. 有大数据开发经验等等。
+
+从来到大学之后,我的好多阅历非常深的老师经常就会告诫我们:“ 一定要有一门自己的特长,不管是技术还好还是其他能力 ” 。我觉得这句话真的非常有道理!
+
+刚刚也提到了要有一门特长,所以在这里再强调一点:公司不需要你什么都会,但是在某一方面你一定要有过于常人的优点。换言之就是我们不需要去掌握每一门技术(你也没精力去掌握这么多技术),而是需要去深入研究某一门技术,对于其他技术我们可以简单了解一下。
diff --git a/docs/essential-content-for-interview/PreparingForInterview/books.md b/docs/essential-content-for-interview/PreparingForInterview/books.md
deleted file mode 100644
index 34f495fd..00000000
--- a/docs/essential-content-for-interview/PreparingForInterview/books.md
+++ /dev/null
@@ -1,66 +0,0 @@
-
-### 核心基础知识
-
-- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。
-- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。
-- [《数据结构与算法分析:C语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。
-- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥!
-- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。
-
-
-
-
-### Java相关
-
-- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。
-- [《Head First Java.第二版》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。
-- [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/): Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。
-- [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。
-- [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。
-- [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。
-- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。
-- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。
-- [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/): 很杂,我只看了前面几章,不太推荐阅读。
-- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。
-- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书!
-- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。
-
-### JavaWeb相关
-
-- [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。
-- [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3
-,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。
-- [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/)(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。
-- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。
-- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价)
-- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。
-- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。
-- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。
-- [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一
-- [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。
-- [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍!
-
-### 操作系统
-
-- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。
-
-### 架构相关
-
-- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。
-- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。
-- [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/):很一般的书籍,我就是当做课后图书来阅读的。
-
-### 代码优化
-
-- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。
-
-### 课外书籍
-
-- 《追风筝的人》(推荐)
-- 《穆斯林的葬礼》 (推荐)
-- 《三体》 (推荐)
-- 《活着——余华》 (推荐)
-
-
-
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
index c99ca1c1..5a091e1c 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
@@ -1,8 +1,34 @@
-这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。
+不论是笔试还是面试都是有章可循的,但是,一定要不要想着如何去应付面试,糊弄面试官,这样做终究是欺骗自己。这篇文章的目的也主要想让大家知道自己应该从哪些方向去准备面试,有哪些可以提高的方向。
-不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。
+网上已经有很多面经了,但是我认为网上的各种面经仅仅只能作为参考,你的实际面试与之还是有一些区别的。另外如果要在网上看别人的面经的话,建议即要看别人成功的案例也要适当看看别人失败的案例。**看面经没问题,不论是你要找工作还是平时学习,这都是一种比较好地检验自己水平的一种方式。但是,一定不要过分寄希望于各种面经,试着去提高自己的综合能力。**
-### 1 如何获取大厂面试机会?
+“ 80% 的 offer 掌握在 20% 的人手 ” 中这句话也不是不无道理的。决定你面试能否成功的因素中实力固然占有很大一部分比例,但是如果你的心态或者说运气不好的话,依然无法拿到满意的 offer。
+
+运气暂且不谈,就拿心态来说,千万不要因为面试失败而气馁或者说怀疑自己的能力,面试失败之后多总结一下失败的原因,后面你就会发现自己会越来越强大。
+
+另外,笔主只是在这里分享一下自己对于 “ 如何备战大厂面试 ” 的一个看法,以下大部分理论/言辞都经过过反复推敲验证,如果有不对的地方或者和你想法不同的地方,请您敬请雅正、不舍赐教。
+
+
+
+- [1 如何获取大厂面试机会?](#1-如何获取大厂面试机会)
+- [2 面试前的准备](#2--面试前的准备)
+ - [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)
+ - [2.2 搞清楚技术面可能会问哪些方向的问题](#22-搞清楚技术面可能会问哪些方向的问题)
+ - [2.2 休闲着装即可](#22-休闲着装即可)
+ - [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)
+ - [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)
+ - [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)
+ - [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)
+ - [2.7 提前准备技术面试](#27-提前准备技术面试)
+ - [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)
+- [3 面试之后复盘](#3-面试之后复盘)
+- [4 如何学习?学会各种框架有必要吗?](#4-如何学习学会各种框架有必要吗)
+ - [4.1 我该如何学习?](#41-我该如何学习)
+ - [4.2 学会各种框架有必要吗?](#42-学会各种框架有必要吗)
+
+
+
+## 1 如何获取大厂面试机会?
**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**
@@ -24,17 +50,39 @@
除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。
-### 2 面试前的准备
+## 2 面试前的准备
### 2.1 准备自己的自我介绍
-从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
+自我介绍一般是你和面试官的第一次面对面正式交流,换位思考一下,假如你是面试官的话,你想听到被你面试的人如何介绍自己呢?一定不是客套地说说自己喜欢编程、平时花了很多时间来学习、自己的兴趣爱好是打球吧?
-我这里简单分享一下我自己的自我介绍的一个简单的模板吧:
+我觉得一个好的自我介绍应该包含这几点要素:
-> 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。
+1. 用简单的话说清楚自己主要的技术栈于擅长的领域;
+2. 把重点放在自己在行的地方以及自己的优势之处;
+3. 重点突出自己的能力比如自己的定位的bug的能力特别厉害;
-### 2.2 关于着装
+从社招和校招两个角度来举例子吧!我下面的两个例子仅供参考,自我介绍并不需要死记硬背,记住要说的要点,面试的时候根据公司的情况临场发挥也是没问题的。另外,网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节和项目经验。
+
+**社招:**
+
+> 面试官,您好!我叫独秀儿。我目前有1年半的工作经验,熟练使用Spring、MyBatis等框架、了解 Java 底层原理比如JVM调优并且有着丰富的分布式开发经验。离开上一家公司是因为我想在技术上得到更多的锻炼。在上一个公司我参与了一个分布式电子交易系统的开发,负责搭建了整个项目的基础架构并且通过分库分表解决了原始数据库以及一些相关表过于庞大的问题,目前这个网站最高支持 10 万人同时访问。工作之余,我利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了Netty进行网络通信, 目前我已经将这个项目开源,在 Github 上收获了 2k的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
+
+**校招:**
+
+> 面试官,您好!我叫秀儿。大学时间我主要利用课外时间学习了 Java 以及 Spring、MyBatis等框架 。在校期间参与过一个考试系统的开发,这个系统的主要用了 Spring、MyBatis 和 shiro 这三种框架。我在其中主要担任后端开发,主要负责了权限管理功能模块的搭建。另外,我在大学的时候参加过一次软件编程大赛,我和我的团队做的在线订餐系统成功获得了第二名的成绩。我还利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了Netty进行网络通信, 目前我已经将这个项目开源,在 Github 上收获了 2k的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
+
+### 2.2 搞清楚技术面可能会问哪些方向的问题
+
+你准备面试的话首先要搞清技术面可能会被问哪些方向的问题吧!
+
+**我直接用思维导图的形式展示出来吧!这样更加直观形象一点,细化到某个知识点的话这张图没有介绍到,留个悬念,下篇文章会详细介绍。**
+
+
+
+**上面思维导图大概涵盖了技术面试可能会设计的技术,但是你不需要把上面的每一个知识点都搞得很熟悉,要分清主次,对于自己不熟悉的技术不要写在简历上,对于自己简单了解的技术不要说自己熟练掌握!**
+
+### 2.2 休闲着装即可
穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。
@@ -57,11 +105,11 @@
1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
2. 在这个项目中你负责了什么、做了什么、担任了什么角色
3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
+4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
### 2.7 提前准备技术面试
-搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
+搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
### 2.7 面试之前做好定向复习
@@ -69,6 +117,35 @@
举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。
-# 3 面试之后复盘
+## 3 面试之后复盘
-如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
\ No newline at end of file
+如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
+
+## 4 如何学习?学会各种框架有必要吗?
+
+### 4.1 我该如何学习?
+
+
+
+最最最关键也是对自己最最最重要的就是学习!看看别人分享的面经,看看我写的这篇文章估计你只需要10分钟不到。但这些东西终究是空洞的理论,最主要的还是自己平时的学习!
+
+如何去学呢?我觉得学习每个知识点可以考虑这样去入手:
+
+1. **官网(大概率是英文,不推荐初学者看)**。
+2. **书籍(知识更加系统完全,推荐)**。
+3. **视频(比较容易理解,推荐,特别是初学的时候。慕课网和哔哩哔哩上面有挺多学习视频可以看,只直接在上面搜索关键词就可以了)**。
+4. **网上博客(解决某一知识点的问题的时候可以看看)**。
+
+这里给各位一个建议,**看视频的过程中最好跟着一起练,要做笔记!!!**
+
+**最好可以边看视频边找一本书籍看,看视频没弄懂的知识点一定要尽快解决,如何解决?**
+
+首先百度/Google,通过搜索引擎解决不了的话就找身边的朋友或者认识的一些人。
+
+#### 4.2 学会各种框架有必要吗?
+
+**一定要学会分配自己时间,要学的东西很多,真的很多,搞清楚哪些东西是重点,哪些东西仅仅了解就够了。一定不要把精力都花在了学各种框架上,算法、数据结构还有计算机网络真的很重要!**
+
+另外,**学习的过程中有一个可以参考的文档很重要,非常有助于自己的学习**。我当初弄 JavaGuide: https://github.com/Snailclimb/JavaGuide 的很大一部分目的就是因为这个。**客观来说,相比于博客,JavaGuide 里面的内容因为更多人的参与变得更加准确和完善。**
+
+如果大家觉得这篇文章不错的话,欢迎给我来个三连(评论+转发+在看)!我会在下一篇文章中介绍如何从技术面时的角度准备面试?
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/PreparingForInterview/应届生面试最爱问的几道Java基础问题.md b/docs/essential-content-for-interview/PreparingForInterview/应届生面试最爱问的几道Java基础问题.md
new file mode 100644
index 00000000..2e93113d
--- /dev/null
+++ b/docs/essential-content-for-interview/PreparingForInterview/应届生面试最爱问的几道Java基础问题.md
@@ -0,0 +1,743 @@
+
+
+- [一 为什么 Java 中只有值传递?](#一-为什么-java-中只有值传递)
+- [二 ==与 equals(重要)](#二-与-equals重要)
+- [三 hashCode 与 equals(重要)](#三-hashcode-与-equals重要)
+ - [3.1 hashCode()介绍](#31-hashcode介绍)
+ - [3.2 为什么要有 hashCode](#32-为什么要有-hashcode)
+ - [3.3 hashCode()与 equals()的相关规定](#33-hashcode与-equals的相关规定)
+ - [3.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?](#34-为什么两个对象有相同的-hashcode-值它们也不一定是相等的)
+- [四 String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?](#四-string-和-stringbufferstringbuilder-的区别是什么string-为什么是不可变的)
+ - [String 为什么是不可变的吗?](#string-为什么是不可变的吗)
+ - [String 真的是不可变的吗?](#string-真的是不可变的吗)
+- [五 什么是反射机制?反射机制的应用场景有哪些?](#五-什么是反射机制反射机制的应用场景有哪些)
+ - [5.1 反射机制介绍](#51-反射机制介绍)
+ - [5.2 静态编译和动态编译](#52-静态编译和动态编译)
+ - [5.3 反射机制优缺点](#53-反射机制优缺点)
+ - [5.4 反射的应用场景](#54-反射的应用场景)
+- [六 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别](#六-什么是-jdk什么是-jre什么是-jvm三者之间的联系与区别)
+ - [6.1 JVM](#61-jvm)
+ - [6.2 JDK 和 JRE](#62-jdk-和-jre)
+- [七 什么是字节码?采用字节码的最大好处是什么?](#七-什么是字节码采用字节码的最大好处是什么)
+- [八 接口和抽象类的区别是什么?](#八-接口和抽象类的区别是什么)
+- [九 重载和重写的区别](#九-重载和重写的区别)
+ - [重载](#重载)
+ - [重写](#重写)
+- [十. Java 面向对象编程三大特性: 封装 继承 多态](#十-java-面向对象编程三大特性-封装-继承-多态)
+ - [封装](#封装)
+ - [继承](#继承)
+ - [多态](#多态)
+- [十一. 什么是线程和进程?](#十一-什么是线程和进程)
+ - [11.1 何为进程?](#111-何为进程)
+ - [11.2 何为线程?](#112-何为线程)
+- [十二. 请简要描述线程与进程的关系,区别及优缺点?](#十二-请简要描述线程与进程的关系区别及优缺点)
+ - [12.1 图解进程和线程的关系](#121-图解进程和线程的关系)
+ - [12.2 程序计数器为什么是私有的?](#122-程序计数器为什么是私有的)
+ - [12.3 虚拟机栈和本地方法栈为什么是私有的?](#123-虚拟机栈和本地方法栈为什么是私有的)
+ - [12.4 一句话简单了解堆和方法区](#124-一句话简单了解堆和方法区)
+- [十三. 说说并发与并行的区别?](#十三-说说并发与并行的区别)
+- [十四. 什么是上下文切换?](#十四-什么是上下文切换)
+- [十五. 什么是线程死锁?如何避免死锁?](#十五-什么是线程死锁如何避免死锁)
+ - [15.1. 认识线程死锁](#151-认识线程死锁)
+ - [15.2 如何避免线程死锁?](#152-如何避免线程死锁)
+- [十六. 说说 sleep() 方法和 wait() 方法区别和共同点?](#十六-说说-sleep-方法和-wait-方法区别和共同点)
+- [十七. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?](#十七-为什么我们调用-start-方法时会执行-run-方法为什么我们不能直接调用-run-方法)
+- [参考](#参考)
+
+
+
+## 一 为什么 Java 中只有值传递?
+
+首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
+
+**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
+
+**下面通过 3 个例子来给大家说明**
+
+> **example 1**
+
+```java
+public static void main(String[] args) {
+ int num1 = 10;
+ int num2 = 20;
+
+ swap(num1, num2);
+
+ System.out.println("num1 = " + num1);
+ System.out.println("num2 = " + num2);
+}
+
+public static void swap(int a, int b) {
+ int temp = a;
+ a = b;
+ b = temp;
+
+ System.out.println("a = " + a);
+ System.out.println("b = " + b);
+}
+```
+
+**结果:**
+
+```
+a = 20
+b = 10
+num1 = 10
+num2 = 20
+```
+
+**解析:**
+
+
+
+在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
+
+**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
+
+> **example 2**
+
+```java
+ public static void main(String[] args) {
+ int[] arr = { 1, 2, 3, 4, 5 };
+ System.out.println(arr[0]);
+ change(arr);
+ System.out.println(arr[0]);
+ }
+
+ public static void change(int[] array) {
+ // 将数组的第一个元素变为0
+ array[0] = 0;
+ }
+```
+
+**结果:**
+
+```
+1
+0
+```
+
+**解析:**
+
+
+
+array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
+
+**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
+
+**很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
+
+> **example 3**
+
+```java
+public class Test {
+
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+ Student s1 = new Student("小张");
+ Student s2 = new Student("小李");
+ Test.swap(s1, s2);
+ System.out.println("s1:" + s1.getName());
+ System.out.println("s2:" + s2.getName());
+ }
+
+ public static void swap(Student x, Student y) {
+ Student temp = x;
+ x = y;
+ y = temp;
+ System.out.println("x:" + x.getName());
+ System.out.println("y:" + y.getName());
+ }
+}
+```
+
+**结果:**
+
+```
+x:小李
+y:小张
+s1:小张
+s2:小李
+```
+
+**解析:**
+
+交换之前:
+
+
+
+交换之后:
+
+
+
+通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
+
+> **总结**
+
+Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
+值传递的。
+
+下面再总结一下 Java 中方法参数的使用情况:
+
+- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
+- 一个方法可以改变一个对象参数的状态。
+- 一个方法不能让对象参数引用一个新的对象。
+
+**参考:**
+
+《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节
+
+## 二 ==与 equals(重要)
+
+**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
+
+**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
+
+- 情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
+- 情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
+
+**举个例子:**
+
+```java
+public class test1 {
+ public static void main(String[] args) {
+ String a = new String("ab"); // a 为一个引用
+ String b = new String("ab"); // b为另一个引用,对象的内容一样
+ String aa = "ab"; // 放在常量池中
+ String bb = "ab"; // 从常量池中查找
+ if (aa == bb) // true
+ System.out.println("aa==bb");
+ if (a == b) // false,非同一对象
+ System.out.println("a==b");
+ if (a.equals(b)) // true
+ System.out.println("aEQb");
+ if (42 == 42.0) { // true
+ System.out.println("true");
+ }
+ }
+}
+```
+
+**说明:**
+
+- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
+- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
+
+## 三 hashCode 与 equals(重要)
+
+面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
+
+### 3.1 hashCode()介绍
+
+hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
+
+```java
+ /**
+ * Returns a hash code value for the object. This method is
+ * supported for the benefit of hash tables such as those provided by
+ * {@link java.util.HashMap}.
+ *
+ * As much as is reasonably practical, the hashCode method defined by
+ * class {@code Object} does return distinct integers for distinct
+ * objects. (This is typically implemented by converting the internal
+ * address of the object into an integer, but this implementation
+ * technique is not required by the
+ * Java™ programming language.)
+ *
+ * @return a hash code value for this object.
+ * @see java.lang.Object#equals(java.lang.Object)
+ * @see java.lang.System#identityHashCode
+ */
+ public native int hashCode();
+```
+
+散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
+
+### 3.2 为什么要有 hashCode
+
+**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
+
+当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
+
+### 3.3 hashCode()与 equals()的相关规定
+
+1. 如果两个对象相等,则 hashcode 一定也是相同的
+2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
+3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
+4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
+5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
+
+### 3.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?
+
+在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
+
+因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
+
+我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
+
+## 四 String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?
+
+**可变性**
+
+简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
+
+StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
+
+AbstractStringBuilder.java
+
+```java
+abstract class AbstractStringBuilder implements Appendable, CharSequence {
+ char[] value;
+ int count;
+ AbstractStringBuilder() {
+ }
+ AbstractStringBuilder(int capacity) {
+ value = new char[capacity];
+ }
+```
+
+**线程安全性**
+
+String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
+
+**性能**
+
+每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
+
+**对于三者使用的总结:**
+
+1. 操作少量的数据: 适用 String
+2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
+3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
+
+#### String 为什么是不可变的吗?
+
+简单来说就是 String 类利用了 final 修饰的 char 类型数组存储字符,源码如下图所以:
+
+```java
+ /** The value is used for character storage. */
+ private final char value[];
+```
+
+#### String 真的是不可变的吗?
+
+我觉得如果别人问这个问题的话,回答不可变就可以了。
+下面只是给大家看两个有代表性的例子:
+
+**1) String 不可变但不代表引用不可以变**
+
+```java
+ String str = "Hello";
+ str = str + " World";
+ System.out.println("str=" + str);
+```
+
+结果:
+
+```
+str=Hello World
+```
+
+解析:
+
+实际上,原来 String 的内容是不变的,只是 str 由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
+
+**2) 通过反射是可以修改所谓的“不可变”对象**
+
+```java
+ // 创建字符串"Hello World", 并赋给引用s
+ String s = "Hello World";
+
+ System.out.println("s = " + s); // Hello World
+
+ // 获取String类中的value字段
+ Field valueFieldOfString = String.class.getDeclaredField("value");
+
+ // 改变value属性的访问权限
+ valueFieldOfString.setAccessible(true);
+
+ // 获取s对象上的value属性的值
+ char[] value = (char[]) valueFieldOfString.get(s);
+
+ // 改变value所引用的数组中的第5个字符
+ value[5] = '_';
+
+ System.out.println("s = " + s); // Hello_World
+```
+
+结果:
+
+```
+s = Hello World
+s = Hello_World
+```
+
+解析:
+
+用反射可以访问私有成员, 然后反射出 String 对象中的 value 属性, 进而改变通过获得的 value 引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
+
+## 五 什么是反射机制?反射机制的应用场景有哪些?
+
+### 5.1 反射机制介绍
+
+JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
+
+### 5.2 静态编译和动态编译
+
+- **静态编译:**在编译时确定类型,绑定对象
+- **动态编译:**运行时确定类型,绑定对象
+
+### 5.3 反射机制优缺点
+
+- **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
+- **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。
+
+### 5.4 反射的应用场景
+
+**反射是框架设计的灵魂。**
+
+在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
+
+举例:① 我们在使用 JDBC 连接数据库时使用 `Class.forName()`通过反射加载数据库的驱动程序;②Spring 框架也用到很多反射机制,最经典的就是 xml 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中;
+2)Java 类里面解析 xml 或 properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的 Class 实例; 4)动态配置实例的属性
+
+**推荐阅读:**
+
+- [Reflection:Java 反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral "Reflection:Java反射机制的应用场景")
+- [Java 基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078 "Java基础之—反射(非常重要)")
+
+## 六 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别
+
+### 6.1 JVM
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
+
+**什么是字节码?采用字节码的好处是什么?**
+
+> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+**Java 程序从源代码到运行一般有下面 3 步:**
+
+
+
+我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
+
+> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
+
+**总结:**
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
+
+### 6.2 JDK 和 JRE
+
+JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
+
+JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
+
+如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
+
+## 七 什么是字节码?采用字节码的最大好处是什么?
+
+**先看下 java 中的编译器和解释器:**
+
+Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为`.class`的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。
+
+Java 源代码---->编译器---->jvm 可执行的 Java 字节码(即虚拟指令)---->jvm---->jvm 中解释器----->机器可执行的二进制机器码---->程序运行。
+
+**采用字节码的好处:**
+
+Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同的计算机上运行。
+
+## 八 接口和抽象类的区别是什么?
+
+1. 接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
+2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
+3. 一个类可以实现多个接口,但最多只能实现一个抽象类
+4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
+5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
+
+注意:Java8 后接口可以有默认实现( default )。
+
+## 九 重载和重写的区别
+
+### 重载
+
+发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
+
+下面是《Java 核心技术》对重载这个概念的介绍:
+
+
+
+### 重写
+
+重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。**也就是说方法提供的行为改变,而方法的外貌并没有改变。**
+
+## 十. Java 面向对象编程三大特性: 封装 继承 多态
+
+### 封装
+
+封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
+
+### 继承
+
+继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
+
+**关于继承如下 3 点请记住:**
+
+1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
+2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
+3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
+
+### 多态
+
+所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
+
+在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
+
+## 十一. 什么是线程和进程?
+
+### 11.1 何为进程?
+
+进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
+
+在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
+
+如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
+
+
+
+### 11.2 何为线程?
+
+线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
+
+Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
+
+```java
+public class MultiThread {
+ public static void main(String[] args) {
+ // 获取 Java 线程管理 MXBean
+ ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+ // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
+ ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
+ // 遍历线程信息,仅打印线程 ID 和线程名称信息
+ for (ThreadInfo threadInfo : threadInfos) {
+ System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
+ }
+ }
+}
+```
+
+上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
+
+```
+[5] Attach Listener //添加事件
+[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
+[3] Finalizer //调用对象 finalize 方法的线程
+[2] Reference Handler //清除 reference 线程
+[1] main //main 线程,程序入口
+```
+
+从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
+
+## 十二. 请简要描述线程与进程的关系,区别及优缺点?
+
+**从 JVM 角度说进程和线程之间的关系**
+
+### 12.1 图解进程和线程的关系
+
+下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](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 "《可能是把 Java 内存区域讲的最清楚的一篇文章》")
+
+
+
+
+
+从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
+
+**总结:** 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
+
+下面是该知识点的扩展内容!
+
+下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
+
+### 12.2 程序计数器为什么是私有的?
+
+程序计数器主要有下面两个作用:
+
+1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
+
+需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
+
+所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
+
+### 12.3 虚拟机栈和本地方法栈为什么是私有的?
+
+- **虚拟机栈:** 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
+- **本地方法栈:** 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
+
+所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
+
+### 12.4 一句话简单了解堆和方法区
+
+堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
+
+## 十三. 说说并发与并行的区别?
+
+- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
+- **并行:** 单位时间内,多个任务同时执行。
+
+## 十四. 什么是上下文切换?
+
+多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
+
+概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
+
+上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
+
+Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
+
+## 十五. 什么是线程死锁?如何避免死锁?
+
+### 15.1. 认识线程死锁
+
+多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
+
+如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
+
+
+
+下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):
+
+```java
+public class DeadLockDemo {
+ private static Object resource1 = new Object();//资源 1
+ private static Object resource2 = new Object();//资源 2
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 1").start();
+
+ new Thread(() -> {
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource1");
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ }
+ }
+ }, "线程 2").start();
+ }
+}
+```
+
+Output
+
+```
+Thread[线程 1,5,main]get resource1
+Thread[线程 2,5,main]get resource2
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 2,5,main]waiting get resource1
+```
+
+线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过`Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
+
+学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
+
+1. 互斥条件:该资源任意一个时刻只由一个线程占用。
+2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
+3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
+4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
+
+### 15.2 如何避免线程死锁?
+
+我们只要破坏产生死锁的四个条件中的其中一个就可以了。
+
+**破坏互斥条件**
+
+这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
+
+**破坏请求与保持条件**
+
+一次性申请所有的资源。
+
+**破坏不剥夺条件**
+
+占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
+
+**破坏循环等待条件**
+
+靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
+
+我们对线程 2 的代码修改成下面这样就不会产生死锁了。
+
+```java
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 2").start();
+```
+
+Output
+
+```
+Thread[线程 1,5,main]get resource1
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 1,5,main]get resource2
+Thread[线程 2,5,main]get resource1
+Thread[线程 2,5,main]waiting get resource2
+Thread[线程 2,5,main]get resource2
+
+Process finished with exit code 0
+```
+
+我们分析一下上面的代码为什么避免了死锁的发生?
+
+线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
+
+## 十六. 说说 sleep() 方法和 wait() 方法区别和共同点?
+
+- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。
+- 两者都可以暂停线程的执行。
+- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
+- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
+
+## 十七. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+
+这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
+
+new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
+
+**总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。**
+
+## 参考
+
+- [https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504 "https://blog.csdn.net/zhzhao999/article/details/53449504")
+- [https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html "https://www.cnblogs.com/skywang12345/p/3324958.html")
+- [https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html "https://www.cnblogs.com/Eason-S/p/5524837.html")
diff --git a/docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md b/docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md
index d07fa52a..a746892f 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/程序员的简历之道.md
@@ -1,21 +1,43 @@
-# 程序员的简历就该这样写
+
-### 1 前言
-一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。
+- [程序员简历就该这样写](#程序员简历就该这样写)
+ - [为什么说简历很重要?](#为什么说简历很重要)
+ - [先从面试前来说](#先从面试前来说)
+ - [再从面试中来说](#再从面试中来说)
+ - [下面这几点你必须知道](#下面这几点你必须知道)
+ - [必须了解的两大法则](#必须了解的两大法则)
+ - [STAR法则(Situation Task Action Result)](#star法则situation-task-action-result)
+ - [FAB 法则(Feature Advantage Benefit)](#fab-法则feature-advantage-benefit)
+ - [项目经历怎么写?](#项目经历怎么写)
+ - [专业技能该怎么写?](#专业技能该怎么写)
+ - [排版注意事项](#排版注意事项)
+ - [其他的一些小tips](#其他的一些小tips)
+ - [推荐的工具/网站](#推荐的工具网站)
-### 2 为什么说简历很重要?
+
-#### 2.1 先从面试前来说
+# 程序员简历就该这样写
-假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
+本篇文章除了教大家用Markdown如何写一份程序员专属的简历,后面还会给大家推荐一些不错的用来写Markdown简历的软件或者网站,以及如何优雅的将Markdown格式转变为PDF格式或者其他格式。
-假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
+推荐大家使用Markdown语法写简历,然后再将Markdown格式转换为PDF格式后进行简历投递。
+
+如果你对Markdown语法不太了解的话,可以花半个小时简单看一下Markdown语法说明: http://www.markdown.cn 。
+
+## 为什么说简历很重要?
+
+一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。为什么说简历很重要呢?
+
+### 先从面试前来说
+
+- 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
+- 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。
-#### 2.2 再从面试中来说
+### 再从面试中来说
我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。
@@ -23,17 +45,16 @@
面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
-### 3 下面这几点你必须知道
+## 下面这几点你必须知道
-1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。
+1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。
2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作**
3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;**
-4. **将自己的项目经历完美的展示出来非常重要。**
+4. **将自己的项目经历完美的展示出来非常重要。**
-### 4 必须了解的两大法则
+## 必须了解的两大法则
-
-**①STAR法则(Situation Task Action Result):**
+### STAR法则(Situation Task Action Result)
- **Situation:** 事情是在什么情况下发生;
- **Task::** 你是如何明确你的任务的;
@@ -42,14 +63,7 @@
简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。
-下面这段内容摘自百度百科,我觉得写的非常不错:
-
-> STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。
-由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容)
-在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。
-但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗?
-
-**②FAB 法则(Feature Advantage Benefit):**
+### FAB 法则(Feature Advantage Benefit)
- **Feature:** 是什么;
- **Advantage:** 比别人好在哪些地方;
@@ -57,7 +71,7 @@
简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。
-### 5 项目经历怎么写?
+## 项目经历怎么写?
简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
@@ -66,7 +80,8 @@
3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-### 6 专业技能该怎么写?
+## 专业技能该怎么写?
+
先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善):
- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握
@@ -79,28 +94,29 @@
- Zookeeper: 掌握
- 常见消息队列: 掌握
- Linux:掌握
-- MySQL常见优化手段:掌握
+- MySQL常见优化手段:掌握
- Spring Boot +Spring Cloud +Docker:了解
- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解
- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉
-### 7 开源程序员Markdown格式简历模板分享
+## 排版注意事项
-分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。
-Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)
+1. 尽量简洁,不要太花里胡哨;
+2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysql,Java不要写成java。这个在我看来还是比较忌讳的,所以一定要注意这个细节;
+3. 中文和数字英文之间加上空格的话看起来会舒服一点;
-
-我的下面这篇文章讲了如何写一份Markdown格式的简历,另外,文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。
-
-[手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd)
-
-### 8 其他的一些小tips
+## 其他的一些小tips
1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
-2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。
-3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
-4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
-5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
-6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
-7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
-8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
+2. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
+3. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
+4. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
+5. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
+6. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
+7. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
+
+## 推荐的工具/网站
+
+- 冷熊简历(MarkDown在线简历工具,可在线预览、编辑和生成PDF):
+- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md)
+- Guide哥自己写的Markdown模板:[https://github.com/Snailclimb/typora-markdown-resume](https://github.com/Snailclimb/typora-markdown-resume)
diff --git a/docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md b/docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md
index 0efdd618..6a57de73 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/美团面试常见问题总结.md
@@ -1,98 +1,96 @@
-
+
- [一 基础篇](#一-基础篇)
- - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)
- - [2. 说一下转发\(Forward\)和重定向\(Redirect\)的区别](#2-说一下转发forward和重定向redirect的区别)
- - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议)
- - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)
- - [为什么要三次握手](#为什么要三次握手)
- - [为什么要传回 SYN](#为什么要传回-syn)
- - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- - [为什么要四次挥手](#为什么要四次挥手)
- - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别)
- - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式)
- - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引)
- - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)
- - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)
- - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗)
- - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)
- - [事务传播行为](#事务传播行为)
- - [隔离级别](#隔离级别)
- - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)
- - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)
+ - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)
+ - [2. 说一下转发(Forward)和重定向(Redirect)的区别](#2-说一下转发forward和重定向redirect的区别)
+ - [3. 在浏览器中输入 url 地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入-url-地址到显示主页的过程整个过程会使用哪些协议)
+ - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)
+ - [为什么要三次握手](#为什么要三次握手)
+ - [为什么要传回 SYN](#为什么要传回-syn)
+ - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
+ - [为什么要四次挥手](#为什么要四次挥手)
+ - [5. IP 地址与 MAC 地址的区别](#5-ip-地址与-mac-地址的区别)
+ - [6. HTTP 请求,响应报文格式](#6-http-请求响应报文格式)
+ - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql 索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql-索引主要使用的两种数据结构什么是覆盖索引)
+ - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)
+ - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)
+ - [10. 简单介绍一下 bean;知道 Spring 的 bean 的作用域与生命周期吗?](#10-简单介绍一下-bean知道-spring-的-bean-的作用域与生命周期吗)
+ - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)
+ - [事务传播行为](#事务传播行为)
+ - [隔离级别](#隔离级别)
+ - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)
+ - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)
- [二 进阶篇](#二-进阶篇)
- - [1 消息队列MQ的套路](#1-消息队列mq的套路)
- - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处)
- - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)
- - [2)降低系统耦合性](#2降低系统耦合性)
- - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)
- - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)
- - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)
- - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)
- - [2.1 两者的对比](#21-两者的对比)
- - [2.2 关于两者的总结](#22-关于两者的总结)
- - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)
- - [3.1 Arraylist 与 LinkedList 有什么不同?\(注意加上从数据结构分析的内容\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)
- - [3.2 HashMap的底层实现](#32-hashmap的底层实现)
- - [1)JDK1.8之前](#1jdk18之前)
- - [2)JDK1.8之后](#2jdk18之后)
- - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)
- - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)
- - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)
+ - [1 消息队列 MQ 的套路](#1-消息队列-mq-的套路)
+ - [1.1 介绍一下消息队列 MQ 的应用场景/使用消息队列的好处](#11-介绍一下消息队列-mq-的应用场景使用消息队列的好处)
+ - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)
+ - [2)降低系统耦合性](#2降低系统耦合性)
+ - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)
+ - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)
+ - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)
+ - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)
+ - [2.1 两者的对比](#21-两者的对比)
+ - [2.2 关于两者的总结](#22-关于两者的总结)
+ - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)
+ - [3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)
+ - [3.2 HashMap 的底层实现](#32-hashmap-的底层实现)
+ - [1)JDK1.8 之前](#1jdk18-之前)
+ - [2)JDK1.8 之后](#2jdk18-之后)
+ - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)
+ - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)
+ - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)
- [三 终结篇](#三-终结篇)
- - [1. Object类有哪些方法?](#1-object类有哪些方法)
- - [1.1 Object类的常见方法总结](#11-object类的常见方法总结)
- - [1.2 hashCode与equals](#12-hashcode与equals)
- - [1.2.1 hashCode\(\)介绍](#121-hashcode介绍)
- - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode)
- - [1.2.3 hashCode\(\)与equals\(\)的相关规定](#123-hashcode与equals的相关规定)
- - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的)
- - [1.3 ==与equals](#13-与equals)
- - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)
- - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)
- - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现)
- - [JDK1.7\(上面有示意图\)](#jdk17上面有示意图)
- - [JDK1.8\(上面有示意图\)](#jdk18上面有示意图)
- - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)
- - [4 线程池了解吗?](#4-线程池了解吗)
- - [4.1 为什么要用线程池?](#41-为什么要用线程池)
- - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)
- - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池)
- - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)
- - [4.3 创建的线程池的方式](#43-创建的线程池的方式)
- - [5 Nginx](#5-nginx)
- - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx)
- - [反向代理](#反向代理)
- - [负载均衡](#负载均衡)
- - [动静分离](#动静分离)
- - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)
- - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)
+ - [1. Object 类有哪些方法?](#1-object-类有哪些方法)
+ - [1.1 Object 类的常见方法总结](#11-object-类的常见方法总结)
+ - [1.2 hashCode 与 equals](#12-hashcode-与-equals)
+ - [1.2.1 hashCode()介绍](#121-hashcode介绍)
+ - [1.2.2 为什么要有 hashCode](#122-为什么要有-hashcode)
+ - [1.2.3 hashCode()与 equals()的相关规定](#123-hashcode与-equals的相关规定)
+ - [1.2.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?](#124-为什么两个对象有相同的-hashcode-值它们也不一定是相等的)
+ - [1.3 ==与 equals](#13-与-equals)
+ - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)
+ - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)
+ - [2.2 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap-线程安全的具体实现方式底层具体实现)
+ - [JDK1.7(上面有示意图)](#jdk17上面有示意图)
+ - [JDK1.8(上面有示意图)](#jdk18上面有示意图)
+ - [3 谈谈 synchronized 和 ReentrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)
+ - [4 线程池了解吗?](#4-线程池了解吗)
+ - [4.1 为什么要用线程池?](#41-为什么要用线程池)
+ - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)
+ - [Java 主要提供了下面 4 种线程池](#java-主要提供了下面-4-种线程池)
+ - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)
+ - [4.3 创建的线程池的方式](#43-创建的线程池的方式)
+ - [5 Nginx](#5-nginx)
+ - [5.1 简单介绍一下 Nginx](#51-简单介绍一下-nginx)
+ - [反向代理](#反向代理)
+ - [负载均衡](#负载均衡)
+ - [动静分离](#动静分离)
+ - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)
+ - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)
-
+
-
-这些问题是2018年去美团面试的同学被问到的一些常见的问题,希望对你有帮助!
+这些问题是 2018 年去美团面试的同学被问到的一些常见的问题,希望对你有帮助!
# 一 基础篇
-
## 1. `System.out.println(3|9)`输出什么?
-正确答案:11.
+正确答案:11。
**考察知识点:&和&&;|和||**
**&和&&:**
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是true时,结果为true;
+共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是 true 时,结果为 true;
-不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为true,是true就继续运算右边的然后判断并输出,是false就停下来直接输出不会再运行后面的东西。
+不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为 true,是 true 就继续运算右边的然后判断并输出,是 false 就停下来直接输出不会再运行后面的东西。
**|和||:**
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true,结果为true,两边都不是true,结果就为false;
+共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为 true,结果为 true,两边都不是 true,结果就为 false;
-不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为true,是true就停下来直接输出不会再运行后面的东西,是false就继续运算右边的然后判断并输出。
+不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为 true,是 true 就停下来直接输出不会再运行后面的东西,是 false 就继续运算右边的然后判断并输出。
**回到本题:**
@@ -102,50 +100,53 @@
**转发是服务器行为,重定向是客户端行为。**
-**转发(Forword)** 通过RequestDispatcher对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
+**转发(Forword)** 通过 RequestDispatcher 对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
```java
request.getRequestDispatcher("login_success.jsp").forward(request, response);
```
-**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
+**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 HttpServletRequestResponse 的 setStatus(int status)方法设置状态码。如果服务器返回 301 或者 302,则浏览器会到新的网址重新请求该资源。
-1. **从地址栏显示来说:** forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
-2. **从数据共享来说:** forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据.
-3. **从运用地方来说:** forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等
-4. **从效率来说:** forward:高. redirect:低.
+1. **从地址栏显示来说**:forward 是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect 是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的 URL。
+2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享 request 里面的数据。redirect:不能共享数据。
+3. **从运用地方来说**:forward:一般用于用户登陆的时候,根据角色转发到相应的模块。redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
+4. **从效率来说**:forward:高。redirect:低。
+## 3. 在浏览器中输入 url 地址到显示主页的过程,整个过程会使用哪些协议
-## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议
+图片来源:《图解 HTTP》:
-图片来源:《图解HTTP》:
-
-
+
总体来说分为以下几个过程:
-1. DNS解析
-2. TCP连接
-3. 发送HTTP请求
-4. 服务器处理请求并返回HTTP报文
+1. DNS 解析
+2. TCP 连接
+3. 发送 HTTP 请求
+4. 服务器处理请求并返回 HTTP 报文
5. 浏览器解析渲染页面
6. 连接结束
具体可以参考下面这篇文章:
-- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
+- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700 "https://segmentfault.com/a/1190000006879700")
+
+> 修正 [issue-568](https://github.com/Snailclimb/JavaGuide/issues/568 "issue-568"):上图中 IP 数据包在路由器之间使用的协议为 OPSF 协议错误,应该为 OSPF 协议 。
+>
+> IP 数据包在路由器之间传播大致分为 IGP 和 BGP 协议,而 IGP 目前主流为 OSPF 协议,思科,华为和 H3C 等主流厂商都有各自实现并使用;BGP 协议为不同 AS(自治系统号)间路由传输,也分为 I-BGP 和 E-BGP,详细资料请查看《TCP/IP 卷一》
## 4. TCP 三次握手和四次挥手
-为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
+为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
**漫画图解:**
-图片来源:《图解HTTP》
-
+图片来源:《图解 HTTP》
+
**简单示意图:**
-
+
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
@@ -159,75 +160,66 @@ request.getRequestDispatcher("login_success.jsp").forward(request, response);
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常
+第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
#### 为什么要传回 SYN
+
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
-> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
-
+> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
#### 传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
-
-
断开一个 TCP 连接则需要“四次挥手”:
- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
-- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
-- 服务器-关闭与客户端的连接,发送一个FIN给客户端
-- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
+- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
+- 服务器-关闭与客户端的连接,发送一个 FIN 给客户端
+- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1
+#### 为什么要四次挥手
-#### 为什么要四次挥手
+任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
-任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
+举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
-举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
+上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891 "https://blog.csdn.net/qzcsu/article/details/72861891")
-上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
+## 5. IP 地址与 MAC 地址的区别
+参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597 "https://blog.csdn.net/guoweimelon/article/details/50858597")
+IP 地址是指互联网协议地址(Internet Protocol Address)IP Address 的缩写。IP 地址是 IP 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
-## 5. IP地址与MAC地址的区别
+MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC 地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的 MAC 地址。
-参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597)
+## 6. HTTP 请求,响应报文格式
-IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
+HTTP 请求报文主要由请求行、请求头部、请求正文 3 部分组成
+HTTP 响应报文主要由状态行、响应头部、响应正文 3 部分组成
+详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273 "https://blog.csdn.net/a19881029/article/details/14002273")
-MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。
-
-## 6. HTTP请求,响应报文格式
-
-
-
-HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
-
-HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
-
-详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273)
-
-## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?
+## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql 索引主要使用的两种数据结构?什么是覆盖索引?
**为什么要使用索引?**
1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
-2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
+2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
3. 帮助服务器避免排序和临时表
-4. 将随机IO变为顺序IO
+4. 将随机 IO 变为顺序 IO
5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?**
-1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
-2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
-3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
+1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
+2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
+3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
**索引是如何提高查询速度的?**
@@ -236,37 +228,36 @@ HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
**说一下使用索引的注意事项**
1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。
-2. 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
+2. 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描
4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
-**Mysql索引主要使用的哪两种数据结构?**
+**Mysql 索引主要使用的哪两种数据结构?**
-- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
-- BTree索引:Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。
+- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。
+- BTree 索引:Mysql 的 BTree 索引使用的是 B 树中的 B+Tree。但对于主要的两种存储引擎(MyISAM 和 InnoDB)的实现方式是不同的。
更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)
**什么是覆盖索引?**
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称
-之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
-
+之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?
- **进程与线程的区别是什么?**
+
+**进程与线程的区别是什么?**
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
**进程间的几种通信方式说一下?**
-
-1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe(无名管道)和fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
+1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为 pipe(无名管道)和 fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
2. **信号量(semophore)**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
3. **消息队列(message queue)**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
4. **信号(signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
-5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
+5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的 IPC 方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
6. **套接字(socket)**:socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
**线程间的几种通信方式知道不?**
@@ -322,34 +313,33 @@ public class Singleton {
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
```java
-public class Singleton {
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return SingletonHolder.INSTANCE;
- }
-}
+public class Singleton {
+ private static class SingletonHolder {
+ private static final Singleton INSTANCE = new Singleton();
+ }
+ private Singleton (){}
+ public static final Singleton getInstance() {
+ return SingletonHolder.INSTANCE;
+ }
+}
```
-## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?
+## 10. 简单介绍一下 bean;知道 Spring 的 bean 的作用域与生命周期吗?
在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
-Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。
+Spring 中的 bean 默认都是单例的,这些单例 Bean 在多线程程序下如何保证线程安全呢? 例如对于 Web 应用来说,Web 容器对于每个用户请求都创建一个单独的 Sevlet 线程来处理请求,引入 Spring 框架之后,每个 Action 都是单例的,那么对于 Spring 托管的单例 Service Bean,如何保证其安全呢? Spring 的单例是基于 BeanFactory 也就是 Spring 容器的,单例 Bean 在此容器内只有一个,Java 的单例是基于 JVM,每个 JVM 内只有一个实例。

-Spring的bean的生命周期以及更多内容可以查看:[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)
-
+Spring 的 bean 的生命周期以及更多内容可以查看:[一文轻松搞懂 Spring 中 bean 的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)
## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?
#### 事务传播行为
事务传播行为(为了解决业务层方法之间互相调用的事务问题):
-当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
+当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:
**支持当前事务的情况:**
@@ -365,24 +355,23 @@ Spring的bean的生命周期以及更多内容可以查看:[一文轻松搞懂
**其他情况:**
-- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
-
+- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。
#### 隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
+- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
+- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
+- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
+- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
## 12. SpringMVC 原理了解吗?

-客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户
+客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户
关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)
@@ -390,116 +379,112 @@ TransactionDefinition 接口中定义了五个表示隔离级别的常量:
过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。
-**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
-
-**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
-
+**IOC:** 控制反转也叫依赖注入。IOC 利用 java 反射机制,AOP 利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在 spring 配置文件中配置对应的 bean 以及设置相关的属性,让 spring 容器来生成类的实例对象以及管理对象。在 spring 容器启动的时候,spring 会把你在配置文件中配置的 bean 都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些 bean 分配给你需要调用这些 bean 的类。
+**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP 可以说是对 OOP 的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现 AOP 的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
# 二 进阶篇
-## 1 消息队列MQ的套路
+## 1 消息队列 MQ 的套路
-消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
+消息队列/消息中间件应该是 Java 程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花 2 个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java 工程师面试突击第 1 季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
-### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处
+### 1.1 介绍一下消息队列 MQ 的应用场景/使用消息队列的好处
-面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
+面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列 MQ 的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**
#### 1)通过异步处理提高系统性能
-
+
+
如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。**
通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
-
+
因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
#### 2)降低系统耦合性
-我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。
+
+我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和 2.**分布式服务**。
> **先来简单说一下分布式服务:**
-目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c)
+目前使用比较多的用来构建**SOA(Service Oriented Architecture 面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解 Dubbo 的可以看我写的关于 Dubbo 的这一篇文章:**《高性能优秀的服务框架-dubbo 介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c "https://juejin.im/post/5acadeb1f265da2375072f9c")
> **再来谈我们的分布式消息队列:**
我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
-
+
+
**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
-**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**
+**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**
-**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
+**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的 ActiveMQ 消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!
### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?
-- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
-- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
+- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
+- **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
-> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
+> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java 工程师面试突击第 1 季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?
+| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
+| :----------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |
+| 单机吞吐量 | 万级,吞吐量比 RocketMQ 和 Kafka 要低了一个数量级 | 万级,吞吐量比 RocketMQ 和 Kafka 要低了一个数量级 | 10 万级,RocketMQ 也是可以支撑高吞吐的一种 MQ | 10 万级别,这是 kafka 最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
+| topic 数量对吞吐量的影响 | | | topic 可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka 尽量保证 topic 数量不要过多。如果要支撑大规模 topic,需要增加更多的机器资源 |
+| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka 是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
+| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到 0 丢失 | 经过参数优化配置,消息可以做到 0 丢失 |
+| 时效性 | ms 级 | 微秒级,这是 rabbitmq 的一大特点,延迟是最低的 | ms 级 | 延迟在 ms 级以内 |
+| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
+| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对 ActiveMQ 5.x 维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang 语言开发,性能极其好,延时很低;吞吐量到万级,MQ 功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用 rabbitmq 也比较多一些但是问题也是显而易见的,RabbitMQ 确实吞吐量会低一些,这是因为他做的实现机制比较重。而且 erlang 开发,国内有几个公司有实力做 erlang 源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复 bug。而且 rabbitmq 集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是 erlang 语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是 ok 的,还可以支撑大规模的 topic 数量,支持复杂 MQ 业务场景。而且一个很大的优势在于,阿里出品都是 java 系的,我们可以自己阅读源码,定制自己公司的 MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的 | kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。而且 kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
-| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafaka |
-| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |
-| 单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
-| topic数量对吞吐量的影响 | | | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 |
-| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
-| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 |
-| 时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
-| 功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
-| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
-
-> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
+> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java 工程师面试突击第 1 季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
### 1.4 关于消息队列其他一些常见的问题展望
-1. 引入消息队列之后如何保证高可用性
+1. 引入消息队列之后如何保证高可用性?
2. 如何保证消息不被重复消费呢?
3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
4. 我该怎么保证从消息队列里拿到的数据按顺序执行?
5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
6. 如果让你来开发一个消息队列中间件,你会怎么设计架构?
-
-
## 2 谈谈 InnoDB 和 MyIsam 两者的区别
### 2.1 两者的对比
-1. **count运算上的区别:** 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存
-2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
-3. **是否支持外键:** MyISAM不支持,而InnoDB支持。
-
+1. **count 运算上的区别:** 因为 MyISAM 缓存有表 meta-data(行数等),因此在做 COUNT(\*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于 InnoDB 来说,则没有这种缓存
+2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行速度比 InnoDB 类型更快,但是不提供事务支持。但是 InnoDB 提供事务支持,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
+3. **是否支持外键:** MyISAM 不支持,而 InnoDB 支持。
### 2.2 关于两者的总结
-MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。
-
-一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。
+MyISAM 更适合读密集的表,而 InnoDB 更适合写密集的表。 在数据库做主从分离的情况下,经常选择 MyISAM 作为主库的存储引擎。
+一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM 的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB 是不错的选择。如果你的数据量很大(MyISAM 支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM 是最好的选择。
## 3 聊聊 Java 中的集合吧!
### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)
- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
-- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);
-- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。**
-- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
-- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
+- **2. 底层数据结构:** Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);
+- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e)`方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1) 而数组为近似 O(n) 。**
+- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。
+- **5. 内存空间占用:** ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
-**补充内容:RandomAccess接口**
+**补充内容:RandomAccess 接口**
```java
public interface RandomAccess {
@@ -508,7 +493,7 @@ public interface RandomAccess {
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
-在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法
+在 binarySearch() 方法中,它要判断传入的 list 是否 RamdomAccess 的实例,如果是,调用 indexedBinarySearch() 方法,如果不是,那么调用 iteratorBinarySearch() 方法
```java
public static
@@ -520,28 +505,27 @@ public interface RandomAccess {
}
```
-ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!
-
+ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?我觉得还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1) ,所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n) ,所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!
**下面再总结一下 list 的遍历方式选择:**
-- 实现了RandomAccess接口的list,优先选择普通for循环 ,其次foreach,
-- 未实现RandomAccess接口的ist, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环
+- 实现了 RandomAccess 接口的 list,优先选择普通 for 循环 ,其次 foreach,
+- 未实现 RandomAccess 接口的 ist, 优先选择 iterator 遍历(foreach 遍历底层也是通过 iterator 实现的),大 size 的数据,千万不要使用普通 for 循环
> Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视!
-### 3.2 HashMap的底层实现
+### 3.2 HashMap 的底层实现
-#### 1)JDK1.8之前
+#### 1)JDK1.8 之前
-JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
+JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
**JDK 1.8 HashMap 的 hash 方法源码:**
-JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
+JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
```java
static final int hash(Object key) {
@@ -552,7 +536,8 @@ JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
```
-对比一下 JDK1.7的 HashMap 的 hash 方法源码.
+
+对比一下 JDK1.7 的 HashMap 的 hash 方法源码.
```java
static int hash(int h) {
@@ -569,22 +554,19 @@ static int hash(int h) {
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
+
+#### 2)JDK1.8 之后
-
+相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。
+
-#### 2)JDK1.8之后
-
-相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
-
-
-
-TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
+TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题!
-### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解
+### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解

@@ -592,21 +574,19 @@ TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红
1. 每个节点非红即黑;
2. 根节点总是黑色的;
-3. 每个叶子节点都是黑色的空节点(NIL节点);
+3. 每个叶子节点都是黑色的空节点(NIL 节点);
4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
-
**红黑树的应用:**
-TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
-
+TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。
+
**为什么要用红黑树**
-
+
简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-### 3.4 红黑树这么优秀,为何不直接使用红黑树得了?
+### 3.4 红黑树这么优秀,为何不直接使用红黑树得了?
说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。
@@ -614,11 +594,11 @@ TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
**HashMap 和 Hashtable 的区别**
-1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
-2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
-3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
-4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
-5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
+1. **线程是否安全:** HashMap 是非线程安全的,Hashtable 是线程安全的;Hashtable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
+2. **效率:** 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;
+3. **对 Null key 和 Null value 的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
+4. **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
+5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
**HashSet 和 HashMap 区别**
@@ -628,13 +608,13 @@ TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
# 三 终结篇
-## 1. Object类有哪些方法?
+## 1. Object 类有哪些方法?
-这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言,大家都要掌握!
+这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握 Java 这门编程语言,大家都要掌握!
-### 1.1 Object类的常见方法总结
+### 1.1 Object 类的常见方法总结
-Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法:
+Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
```java
@@ -661,15 +641,15 @@ protected void finalize() throws Throwable { }//实例被垃圾回收器回收
```
-> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode与equals”相关的问题。
+> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode 与 equals”相关的问题。
-### 1.2 hashCode与equals
+### 1.2 hashCode 与 equals
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
+面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
#### 1.2.1 hashCode()介绍
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
+hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
```java
public native int hashCode();
@@ -677,41 +657,38 @@ hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-#### 1.2.2 为什么要有hashCode
+#### 1.2.2 为什么要有 hashCode
+**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
-**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:**
+当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
+#### 1.2.3 hashCode()与 equals()的相关规定
+1. 如果两个对象相等,则 hashcode 一定也是相同的
+2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
+3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
+4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
+5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-#### 1.2.3 hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?
+#### 1.2.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?
在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
+因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-> ==与equals 的对比也是比较常问的基础问题之一!
+> ==与 equals 的对比也是比较常问的基础问题之一!
-### 1.3 ==与equals
+### 1.3 ==与 equals
**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。
-
+- 情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
+- 情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
**举个例子:**
@@ -737,10 +714,10 @@ public class test1 {
**说明:**
-- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
-- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
+- String 中的 equals()方法是被重写过的,因为 Object 的 equals()方法是比较的对象的内存地址,而 String 的 equals()方法比较的是对象的值。
+- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
-> 在[【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视!
+> 在[【备战春招/秋招系列 5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视!
## 2 ConcurrentHashMap 相关问题
@@ -748,23 +725,23 @@ public class test1 {
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
-- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
-- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
+- **底层数据结构:** JDK1.7 的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
+- **实现线程安全的方式(重要):** ① **在 JDK1.7 的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配 16 个 Segment,比 Hashtable 效率提高 16 倍。) **到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)**:使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
-**两者的对比图:**
+**两者的对比图:**
图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html
-HashTable:
+Hashtable:

-JDK1.7的ConcurrentHashMap:
+JDK1.7 的 ConcurrentHashMap:

-JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点
+JDK1.8 的 ConcurrentHashMap(TreeBin: 红黑二叉树节点
Node: 链表节点):

-### 2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现
+### 2.2 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
#### JDK1.7(上面有示意图)
@@ -772,54 +749,52 @@ Node: 链表节点):
**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
-Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
+Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
```java
static class Segment extends ReentrantLock implements Serializable {
}
```
-一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
+一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。
#### JDK1.8(上面有示意图)
-ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
+ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。
-synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
+synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
-## 3 谈谈 synchronized 和 ReenTrantLock 的区别
+## 3 谈谈 synchronized 和 ReentrantLock 的区别
**① 两者都是可重入锁**
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
+两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。
-**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**
+**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
+synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-**③ ReenTrantLock 比 synchronized 增加了一些高级功能**
+**③ ReentrantLock 比 synchronized 增加了一些高级功能**
-相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
+相比 synchronized,ReentrantLock 增加了一些高级功能。主要来说主要有三点:**① 等待可中断;② 可实现公平锁;③ 可实现选择性通知(锁可以绑定多个条件)**
-- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
+- **ReentrantLock 提供了一种能够中断等待锁的线程的机制**,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
+- **ReentrantLock 可以指定是公平锁还是非公平锁。而 synchronized 只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock 默认情况是非公平的,可以通过 ReentrantLock 类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
+- synchronized 关键字与 wait()和 notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock 类当然也可以实现,但是需要借助于 Condition 接口与 newCondition() 方法。Condition 是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个 Lock 对象中可以创建多个 Condition 实例(即对象监视器),**线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用 notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用 ReentrantLock 类结合 Condition 实例可以实现“选择性通知”** ,这个功能非常重要,而且是 Condition 接口默认提供的。而 synchronized 关键字就相当于整个 Lock 对象中只有一个 Condition 实例,所有的线程都注册在它一个身上。如果执行 notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而 Condition 实例的 signalAll()方法 只会唤醒注册在该 Condition 实例中的所有等待线程。
-如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
+如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。
**④ 两者的性能已经相差无几**
-在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。
-
+在 JDK1.6 之前,synchronized 的性能是比 ReentrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而 ReentrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReentrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReentrantLock 的文章都是错的!JDK1.6 之后,性能已经不是选择 synchronized 和 ReentrantLock 的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的 synchronized,所以还是提倡在 synchronized 能满足你的需求的情况下,优先考虑使用 synchronized 关键字来进行同步!优化后的 synchronized 和 ReentrantLock 一样,在很多地方都是用到了 CAS 操作。
## 4 线程池了解吗?
-
### 4.1 为什么要用线程池?
-线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
+线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
-这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处:
+这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
@@ -827,26 +802,26 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团
### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?
-#### Java 主要提供了下面4种线程池
+#### Java 主要提供了下面 4 种线程池
- **FixedThreadPool:** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
-- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor又分为:ScheduledThreadPoolExecutor(包含多个线程)和SingleThreadScheduledExecutor (只包含一个线程)两种。
+- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor 又分为:ScheduledThreadPoolExecutor(包含多个线程)和 SingleThreadScheduledExecutor (只包含一个线程)两种。
#### 各种线程池的适用场景介绍
- **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
-- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。
+- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景;
- **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
-- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景,
+- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
- **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。
### 4.3 创建的线程池的方式
**(1) 使用 Executors 创建**
-我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池,另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
+我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用 Java 提供好的线程池,另外在《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
```java
Executors 返回线程池对象的弊端如下:
@@ -855,8 +830,8 @@ FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Inte
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
```
-**(2) ThreadPoolExecutor的构造函数创建**
+**(2) ThreadPoolExecutor 的构造函数创建**
我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下:
@@ -866,11 +841,11 @@ private static ExecutorService executor = new ThreadPoolExecutor(13, 13,
new ArrayBlockingQueue(13));
```
-这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
+这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出 java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
**(3) 使用开源类库**
-Hollis 大佬之前在他的文章中也提到了:“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例:
+Hollis 大佬之前在他的文章中也提到了:“除了自己定义 ThreadPoolExecutor 外。还有其他方法。这个时候第一时间就应该想到开源类库,如 apache 和 guava 等。”他推荐使用 guava 提供的 ThreadFactoryBuilder 来创建线程池。下面是参考他的代码示例:
```java
public class ExecutorsDemo {
@@ -891,19 +866,19 @@ public class ExecutorsDemo {
}
```
-通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
+通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
-## 5 Nginx
+## 5 Nginx
-### 5.1 简单介绍一下Nginx
+### 5.1 简单介绍一下 Nginx
-Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
+Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
#### 反向代理
谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
-- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。
+- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的 ip 和端口号。正向代理比较常见的一个例子就是 VPN 了。
- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/):
@@ -918,9 +893,9 @@ Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(I
在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
-Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。
+Nginx 支持的 weight 轮询(默认)、ip_hash、fair、url_hash 这四种负载均衡调度算法,感兴趣的可以自行查阅。
-负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
+负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
#### 动静分离
@@ -928,23 +903,23 @@ Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负
### 5.2 为什么要用 Nginx?
-> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
+> 这部分内容参考极客时间—[Nginx 核心知识 100 讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE= "Nginx核心知识100讲的内容")。
如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。
-Nginx 有以下5个优点:
+Nginx 有以下 5 个优点:
-1. 高并发、高性能(这是其他web服务器不具有的)
+1. 高并发、高性能(这是其他 web 服务器不具有的)
2. 可扩展性好(模块化设计,第三方插件生态圈丰富)
3. 高可靠性(可以在服务器行持续不间断的运行数年)
-4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx)
-5. BSD许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本)
+4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx 服务的情况下升级 Nginx)
+5. BSD 许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本)
### 5.3 Nginx 的四个主要组成部分了解吗?
-> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。
+> 这部分内容参考极客时间—[Nginx 核心知识 100 讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE= "Nginx核心知识100讲的内容")。
- Nginx 二进制可执行文件:由各模块源码编译出一个文件
-- Nginx.conf 配置文件:控制Nginx 行为
-- acess.log 访问日志: 记录每一条HTTP请求信息
-- error.log 错误日志:定位问题
+- nginx.conf 配置文件:控制 Nginx 行为
+- acess.log 访问日志: 记录每一条 HTTP 请求信息
+- error.log 错误日志:定位问题
diff --git a/docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md b/docs/essential-content-for-interview/PreparingForInterview/面试官-你有什么问题要问我.md
similarity index 88%
rename from docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md
rename to docs/essential-content-for-interview/PreparingForInterview/面试官-你有什么问题要问我.md
index d4d6b64b..7a55d539 100644
--- a/docs/essential-content-for-interview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md
+++ b/docs/essential-content-for-interview/PreparingForInterview/面试官-你有什么问题要问我.md
@@ -1,4 +1,4 @@
-我还记得当时我去参加面试的时候,几乎每一场面试,特别是HR面和高管面的时候,面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊?
+我还记得当时我去参加面试的时候,几乎每一场面试,特别是HR面和高管面的时候,面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊?

@@ -14,7 +14,7 @@
### 真诚一点,不要问太 Low 的问题
-回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太Low的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不伤心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题:
+回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太 Low 的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不上心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题:
- 贵公司的主要业务是什么?(面试之前自己不知道提前网上查一下吗?)
- 贵公司的男女比例如何?(考虑脱单?记住你是来工作的!)
@@ -28,9 +28,9 @@
#### 面对HR或者其他Level比较低的面试官时
1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答,不会让面试官陷入无话可说的尴尬境地。另外,从面试官的回答中你可以加深对这个公司的了解,让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外,这样的问题在某种程度上还可以拉进你与面试官的距离。)
-2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法,)
+2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法。)
3. **我觉得我这次表现的不是太好,你有什么建议或者评价给我吗?**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说,这样可以显的你比较谦虚好学上进。)
-4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。)
+4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。)
5. **这个岗位为什么还在招人?** (岗位真实性和价值咨询)
6. **大概什么时候能给我回复呢?** (终面的时候,如果面试官没有说的话,可以问一下)
7. ......
diff --git a/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md b/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md
new file mode 100644
index 00000000..a1f61011
--- /dev/null
+++ b/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md
@@ -0,0 +1,222 @@
+本文的内容都是根据读者投稿的真实面试经历改编而来,首次尝试这种风格的文章,花了几天晚上才总算写完,希望对你有帮助。
+
+本文主要涵盖下面的内容:
+
+1. 分布式商城系统:架构图讲解;
+2. 消息队列相关:削峰和解耦;
+3. Redis 相关:缓存穿透问题的解决;
+4. 一些基础问题:
+ - 网络相关:1.浏览器输入 URL 发生了什么? 2.TCP 和 UDP 区别? 3.TCP 如何保证传输可靠性?
+ - Java 基础:1. 既然有了字节流,为什么还要有字符流? 2.深拷贝 和 浅拷贝有啥区别呢?
+
+下面是正文!
+
+面试开始,坐在我前面的就是这次我的面试官吗?这发量看着根本不像程序员啊?我心里正嘀咕着,只听见面试官说:“小伙,下午好,我今天就是你的面试官,咱们开始面试吧!”。
+
+### 第一面开始
+
+**面试官:** 我也不用多说了,你先自我介绍一下吧,简历上有的就不要再说了哈。
+
+**我:** 内心 os:"果然如我所料,就知道会让我先自我介绍一下,还好我看了 [JavaGuide](https://github.com/Snailclimb/JavaGuide "JavaGuide") ,学到了一些套路。套路总结起来就是:**最好准备好两份自我介绍,一份对 hr 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。** 所以,我按照这个套路准备了一个还算通用的模板,毕竟我懒嘛!不想多准备一个自我介绍,整个通用的多好!
+
+> 面试官,您好!我叫小李子。大学时间我主要利用课外时间学习 Java 相关的知识。在校期间参与过一个某某系统的开发,主要负责数据库设计和后端系统开发.,期间解决了什么问题,巴拉巴拉。另外,我自己在学习过程中也参照网上的教程写过一个电商系统的网站,写这个电商网站主要是为了能让自己接触到分布式系统的开发。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在已经是某社区的认证作者,写过一系列关于 线程池使用以及源码分析的文章深受好评。另外,我获得过省级编程比赛二等奖,我将这个获奖项目开源到 Github 还收获了 2k 的 Star 呢?
+
+**面试官:** 你刚刚说参考网上的教程做了一个电商系统?你能画画这个电商系统的架构图吗?
+
+**我:** 内心 os: "这可难不倒我!早知道写在简历上的项目要重视了,提前都把这个系统的架构图画了好多遍了呢!"
+
+
+
+做过分布式电商系统的一定很熟悉上面的架构图(目前比较流行的是微服务架构,但是如果你有分布式开发经验也是非常加分的!)。
+
+**面试官:** 简单介绍一下你做的这个系统吧!
+
+**我:** 我一本正经的对着我刚刚画的商城架构图开始了满嘴造火箭的讲起来:
+
+> 本系统主要分为展示层、服务层和持久层这三层。表现层顾名思义主要就是为了用来展示,比如我们的后台管理系统的页面、商城首页的页面、搜索系统的页面等等,这一层都只是作为展示,并没有提供任何服务。
+>
+> 展示层和服务层一般是部署在不同的机器上来提高并发量和扩展性,那么展示层和服务层怎样才能交互呢?在本系统中我们使用 Dubbo 来进行服务治理。Dubbo 是一款高性能、轻量级的开源 Java RPC 框架。Dubbo 在本系统的主要作用就是提供远程 RPC 调用。在本系统中服务层的信息通过 Dubbo 注册给 ZooKeeper,表现层通过 Dubbo 去 ZooKeeper 中获取服务的相关信息。Zookeeper 的作用仅仅是存放提供服务的服务器的地址和一些服务的相关信息,实现 RPC 远程调用功能的还是 Dubbo。如果需要引用到某个服务的时候,我们只需要在配置文件中配置相关信息就可以在代码中直接使用了,就像调用本地方法一样。假如说某个服务的使用量增加时,我们只用为这单个服务增加服务器,而不需要为整个系统添加服务。
+>
+> 另外,本系统的数据库使用的是常用的 MySQL,并且用到了数据库中间件 MyCat。另外,本系统还用到 redis 内存数据库来作为缓存来提高系统的反应速度。假如用户第一次访问数据库中的某些数据,这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
+>
+> 系统还用到了 Elasticsearch 来提供搜索功能。使用 Elasticsearch 我们可以非常方便的为我们的商城系统添加必备的搜索功能,并且使用 Elasticsearch 还能提供其它非常实用的功能,并且很容易扩展。
+
+**面试官:** 我看你的系统里面还用到了消息队列,能说说为什么要用它吗?
+
+**我:**
+
+> 使用消息队列主要是为了:
+>
+> 1. 减少响应所需时间和削峰。
+> 2. 降低系统耦合性(解耦/提升系统可扩展性)。
+
+**面试官:** 你这说的太简单了!能不能稍微详细一点,最好能画图给我解释一下。
+
+**我:** 内心 os:"都 2019 年了,大部分面试者都能对消息队列的为系统带来的这两个好处倒背如流了,如果你想走的更远就要别别人懂的更深一点!"
+
+> 当我们不使用消息队列的时候,所有的用户的请求会直接落到服务器,然后通过数据库或者缓存响应。假如在高并发的场景下,如果没有缓存或者数据库承受不了这么大的压力的话,就会造成响应速度缓慢,甚至造成数据库宕机。但是,在使用消息队列之后,用户的请求数据发送给了消息队列之后就可以立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库,不过要确保消息不被重复消费还要考虑到消息丢失问题。由于消息队列服务器处理速度快于数据库,因此响应速度得到大幅改善。
+>
+> 文字 is too 空洞,直接上图吧!下图展示了使用消息前后系统处理用户请求的对比(ps:我自己都被我画的这个图美到了,如果你也觉得这张图好看的话麻烦来个素质三连!)。
+>
+> 
+>
+> 通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
+>
+> 
+>
+> 使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。还是直接上图吧:
+>
+> 
+>
+> 生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合, 这显然也提高了系统的扩展性。
+
+**面试官:** 你觉得它有什么缺点吗?或者说怎么考虑用不用消息队列?
+
+**我:** 内心 os: "面试官真鸡贼!这不是勾引我上钩么?还好我准备充分。"
+
+> 我觉得可以从下面几个方面来说:
+>
+> 1. **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
+> 2. **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
+> 3. **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
+
+**面试官**:做项目的过程中遇到了什么问题吗?解决了吗?如果解决的话是如何解决的呢?
+
+**我** : 内心 os: "做的过程中好像也没有遇到什么问题啊!怎么办?怎么办?突然想到可以说我在使用 Redis 过程中遇到的问题,毕竟我对 Redis 还算熟悉嘛,**把面试官往这个方向吸引**,准没错。"
+
+> 我在使用 Redis 对常用数据进行缓冲的过程中出现了缓存穿透问题。然后,我通过谷歌搜索相关的解决方案来解决的。
+
+**面试官:** 你还知道缓存穿透啊?不错啊!来说说什么是缓存穿透以及你最后的解决办法。
+
+**我:** 我先来谈谈什么是缓存穿透吧!
+
+> 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
+>
+> 总结一下就是:
+>
+> 1. 缓存层不命中。
+> 2. 存储层不命中,不将空结果写回缓存。
+> 3. 返回空结果给客户端。
+>
+> 一般 MySQL 默认的最大连接数在 150 左右,这个可以通过 `show variables like '%max_connections%';`命令来查看。最大连接数一个还只是一个指标,cpu,内存,磁盘,网络等物理条件都是其运行指标,这些指标都会限制其并发能力!所以,一般 3000 的并发请求就能打死大部分数据库了。
+
+**面试官:** 小伙子不错啊!还准备问你:“为什么 3000 的并发能把支持最大连接数 4000 数据库压死?”想不到你自己就提前回答了!不错!
+
+**我:** 别夸了!别夸了!我再来说说我知道的一些解决办法以及我最后采用的方案吧!您帮忙看看有没有问题。
+
+> 最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
+>
+> 参数校验通过的情况还是会出现缓存穿透,我们还可以通过以下几个方案来解决这个问题:
+>
+> **1)缓存无效 key** : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如何黑客恶意攻击,每次构建的不同的请求 key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
+>
+> 另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值`。
+>
+> **2)布隆过滤器:** 布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
+
+**面试官:** 不错不错!你还知道布隆过滤器啊!来给我谈一谈。
+
+**我:** 内心 os:“如果你准备过海量数据处理的面试题,你一定对:“如何确定一个数字是否在于包含大量数字的数字集中(数字集很大,5 亿以上!)?”这个题目很了解了!解决这道题目就要用到布隆过滤器。”
+
+> 布隆过滤器在针对海量数据去重或者验证数据合法性的时候非常有用。**布隆过滤器的本质实际上是 “位(bit)数组”,也就是说每一个存入布隆过滤器的数据都只占一位。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。**
+>
+> **当一个元素加入布隆过滤器中的时候,会进行如下操作:**
+>
+> 1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
+> 2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
+>
+> **当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:**
+>
+> 1. 对给定元素再次进行相同的哈希计算;
+> 2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
+>
+> 举个简单的例子:
+>
+> 
+>
+> 如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为 0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。
+>
+> 如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
+>
+> **不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。**
+>
+> 综上,我们可以得出:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
+
+**面试官:** 看来你对布隆过滤器了解的还挺不错的嘛!那你快说说你最后是怎么利用它来解决缓存穿透的。
+
+**我:** 知道了布隆过滤器的原理就之后就很容易做了。我是利用 Redis 布隆过滤器来做的。我把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,我会先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。总结一下就是下面这张图(这张图片不是我画的,为了省事直接在网上找的):
+
+
+
+更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md "《不了解布隆过滤器?一文给你整的明明白白!》") ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
+
+**面试官:** 好了好了。项目就暂时问到这里吧!下面有一些比较基础的问题我简单地问一下你。内心 os: 难不成这家伙满口高并发,连最基础的东西都不会吧!
+
+**我:** 好的好的!没问题!
+
+**面试官:** 浏览器输入 URL 发生了什么?
+
+**我:** 内心 os:“很常问的一个问题,建议拿小本本记好了!另外,百度好像最喜欢问这个问题,去百度面试可要提前备好这道题的功课哦!相似问题:打开一个网页,整个过程会使用哪些协议?”。
+
+> 图解(图片来源:《图解 HTTP》):
+>
+>
+>
+> 总体来说分为以下几个过程:
+>
+> 1. DNS 解析
+> 2. TCP 连接
+> 3. 发送 HTTP 请求
+> 4. 服务器处理请求并返回 HTTP 报文
+> 5. 浏览器解析渲染页面
+> 6. 连接结束
+>
+> 具体可以参考下面这篇文章:
+>
+> - [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700 "https://segmentfault.com/a/1190000006879700")
+
+**面试官:** TCP 和 UDP 区别?
+
+**我:**
+
+> 
+>
+> UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
+>
+> TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
+
+**面试官:** TCP 如何保证传输可靠性?
+
+**我:**
+
+> 1. 应用数据被分割成 TCP 认为最适合发送的数据块。
+> 2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
+> 3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
+> 4. TCP 的接收端会丢弃重复的数据。
+> 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
+> 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
+> 7. **ARQ 协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
+> 8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
+
+**面试官:** 我再来问你一些 Java 基础的问题吧!小伙子。
+
+**我:** 好的。(内心 os:“你尽管来!”)
+
+**面试官:** 既然有了字节流,为什么还要有字符流?
+
+我:内心 os :“问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**”
+
+> 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
+
+**面试官**:深拷贝 和 浅拷贝有啥区别呢?
+
+**我:**
+
+> 1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
+> 2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
+>
+> 
+
+**面试官:** 好的!面试结束。小伙子可以的!回家等通知吧!
+
+**我:** 好的好的!辛苦您了!
diff --git a/docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md b/docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md
index b6f40fbc..00aaecd8 100644
--- a/docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md
+++ b/docs/essential-content-for-interview/面试必备之乐观锁与悲观锁.md
@@ -1,3 +1,22 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [何谓悲观锁与乐观锁](#何谓悲观锁与乐观锁)
+ - [悲观锁](#悲观锁)
+ - [乐观锁](#乐观锁)
+ - [两种锁的使用场景](#两种锁的使用场景)
+- [乐观锁常见的两种实现方式](#乐观锁常见的两种实现方式)
+ - [1. 版本号机制](#1-版本号机制)
+ - [2. CAS算法](#2-cas算法)
+- [乐观锁的缺点](#乐观锁的缺点)
+ - [1 ABA 问题](#1-aba-问题)
+ - [2 循环时间长开销大](#2-循环时间长开销大)
+ - [3 只能保证一个共享变量的原子操作](#3-只能保证一个共享变量的原子操作)
+- [CAS与synchronized的使用情景](#cas与synchronized的使用情景)
+
+
+
### 何谓悲观锁与乐观锁
> 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
@@ -64,8 +83,6 @@ JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。
-
-
### CAS与synchronized的使用情景
> **简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)**
@@ -73,10 +90,17 @@ CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS
1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
-
补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 **“重量级锁”** 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁** 和 **轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞**,**竞争切换后继续竞争锁**,**稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
-
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/github-trending/2019-12.md b/docs/github-trending/2019-12.md
new file mode 100644
index 00000000..9a8f79ed
--- /dev/null
+++ b/docs/github-trending/2019-12.md
@@ -0,0 +1,144 @@
+# 年末将至,值得你关注的16个Java 开源项目!
+
+Star 的数量统计于 2019-12-29。
+
+### 1.JavaGuide
+
+Guide 哥大三开始维护的,目前算是纯 Java 类型项目中 Star 数量最多的项目了。但是,本仓库的价值远远(+N次 )比不上像 Spring Boot、Elasticsearch 等等这样非常非常非常优秀的项目。希望以后我也有能力为这些项目贡献一些有价值的代码。
+
+- **Github 地址**:
+- **Star**: 66.3k
+- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+
+### 2.java-design-patterns
+
+感觉还不错。根据官网介绍:
+
+> 设计模式是程序员在设计应用程序或系统时可以用来解决常见问题的最佳形式化实践。 设计模式可以通过提供经过测试的,经过验证的开发范例来加快开发过程。 重用设计模式有助于防止引起重大问题的细微问题,并且还可以提高熟悉模式的编码人员和架构师的代码可读性。
+
+
+
+- **Github 地址** : [https://github.com/iluwatar/java-design-patterns](https://github.com/iluwatar/java-design-patterns)
+- **Star**: 53.8k
+- **介绍**: 用 Java 实现的设计模式。[https://java-design-patterns.com](https://java-design-patterns.com/)。
+
+### 3.elasticsearch
+
+搜索引擎界的扛把子,但不仅仅是搜素引擎那么简单。
+
+- **Github 地址** : [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
+- **Star**: 46.2k
+- **介绍**: 开源,分布式,RESTful 搜索引擎。
+
+### 4.spring-boot
+
+必须好好学啊,一定要好好学!现在 Java 后端新项目有不用 Spring Boot 开发的有吗?如果有的话,请把这个人的联系方式告诉我,我有很多话想给他交流交流!
+
+- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
+- **star:** 34.8k (1,073 stars this month)
+- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
+
+### 5.RxJava
+
+这个没怎么用过,不做太多评价。
+
+- **Github 地址** : [https://github.com/ReactiveX/RxJava](https://github.com/ReactiveX/RxJava)
+- **Star**: 41.5k
+- **介绍**: `RxJava` 是一个 基于事件流、实现异步操作的库。
+
+### 6.advanced-java
+
+本项目大部分内容来自中华石杉的一个课程,内容涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识,非常不错了!
+
+- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
+- **Star**: 36.7k
+- **介绍**: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
+
+### 7.mall
+
+很牛逼的实战项目,还附有详细的文档,作为毕设或者练手项目都再好不过了。
+
+- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
+- **star**: 27.6k
+- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
+
+### 8.okhttp
+
+给我感觉是安卓项目中用的居多。当然,Java 后端项目也会经常用,但是一般使用 Spring Boot 进行开发的时候,如果需要远程调用的话建议使用 Spring 封装的 `RestTemplate `。
+
+- **Github地址**:[https://github.com/square/okhttp](https://github.com/square/okhttp)
+- **star**: 35.4k
+- **介绍**: 适用于Android,Kotlin和Java的HTTP客户端。https://square.github.io/okhttp/。
+
+### 9.guava
+
+很厉害很厉害!提供了很多非常实用的工具类、更加实用的集合类、一些常用的数据结构比如布隆过滤器、缓存等等。
+
+- **Github地址**:[https://github.com/google/guava](https://github.com/google/guava)
+- **star**: 35.3k
+- **介绍**: Guava是一组核心库,其中包括新的集合类型(例如 multimap 和 multiset),不可变集合,图形库以及用于并发,I / O,哈希,基元,字符串等的实用程序!
+
+### 10.Spark
+
+我木有用过,留下了没有技术的眼泪。
+
+- **Github地址**:[https://github.com/apache/spark](https://github.com/apache/spark)
+- **star**: 24.7k
+- **介绍**: Spark 是一个快速、通用的大规模数据处理引擎,和Hadoop的MapReduce计算框架类似,但是相对于MapReduce,Spark凭借其可伸缩、基于内存计算等特点,以及可以直接读写Hadoop上任何格式数据的优势,进行批处理时更加高效,并有更低的延迟。
+
+### 11.arthas
+
+虽然我自己没有亲身用过,但是身边用过的朋友评价都还挺好的。根据官网介绍,这家伙可以解决下面这些让人脑壳疼的问题:
+
+1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
+2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
+3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
+4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
+5. 是否有一个全局视角来查看系统的运行状况?
+6. 有什么办法可以监控到JVM的实时运行状态?
+7. 怎么快速定位应用的热点,生成火焰图?
+
+- **Github 地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
+- **star**: 18.8 k
+- **介绍**: Arthas 是 Alibaba 开源的 Java 诊断工具。
+
+### 12.spring-boot-examples
+
+学习 Spring Boot 必备!配合上我的 **springboot-guide** :[https://github.com/Snailclimb/springboot-guide](https://github.com/Snailclimb/springboot-guide),效果杠杠滴!
+
+- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
+- **star**: 20.2 k
+- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
+
+### 13.lombok
+
+使用 Lombok 我们可以简化我们的 Java 代码,比如使用它之后我们通过注释就可以实现 getter/setter、equals等方法。
+
+- **Github 地址**:[https://github.com/rzwitserloot/lombok](https://github.com/rzwitserloot/lombok)
+- **star**: 20.2 k
+- **介绍**: 对 Java 编程语言的非常刺激的补充。[https://projectlombok.org/](https://projectlombok.org/) 。
+
+### 14.p3c
+
+与我而言,没有特别惊艳,但是一些提供的一些代码规范确实挺有用的!
+
+- **Github 地址**:[https://github.com/alibaba/p3c](https://github.com/alibaba/p3c)
+- **star**: 19.8 k
+- **介绍**: 阿里巴巴Java编码指南pmd实现和IDE插件。
+
+### 15.spring-boot-demo
+
+- **Github 地址**:[https://github.com/xkcoding/spring-boot-demo](https://github.com/xkcoding/spring-boot-demo)
+- **Star**: 8.8k
+- **介绍**: spring boot demo 是一个用来深度学习并实战 spring boot 的项目。
+
+### 16. awesome-java
+
+Guide 哥半个多月前开始维护的,虽然现在 Star 数量比较少,我相信后面一定会有更多人喜欢上这个项目,我也会继续认真维护下去。
+
+- **Github 地址**:[https://github.com/Snailclimb/awesome-java](https://github.com/Snailclimb/awesome-java)
+- **Star**: 0.3 k
+- **介绍**: Github 上非常棒的 Java 开源项目集合。
+
+
+
diff --git a/docs/github-trending/2019-3.md b/docs/github-trending/2019-3.md
new file mode 100644
index 00000000..eaed4a5d
--- /dev/null
+++ b/docs/github-trending/2019-3.md
@@ -0,0 +1,60 @@
+### 1. JavaGuide
+
+- **Github 地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
+- **Star**: 32.9k (6,196 stars this month)
+- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+
+### 2.advanced-java
+
+- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
+- **Star**: 15.1k (4,012 stars this month)
+- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
+
+### 3.spring-boot-examples
+
+- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
+- **Star**: 12.8k (3,462 stars this month)
+- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
+
+### 4. mall
+
+- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
+- **star**: 9.7 k (2,418 stars this month)
+- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
+
+### 5. seata
+
+- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
+- **star**: 7.2 k (1359 stars this month)
+- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
+
+### 6. quarkus
+
+- **Github 地址**:[https://github.com/quarkusio/quarkus](https://github.com/quarkusio/quarkus)
+- **star**: 12 k (1,224 stars this month)
+- **介绍**: Quarkus 是为 GraalVM 和 HotSpot 量身定制的 Kubernetes Native Java 框架,由最佳的 Java 库和标准精心打造而成。Quarkus 的目标是使 Java 成为 Kubernetes 和无服务器环境中的领先平台,同时为开发人员提供统一的反应式和命令式编程模型,以优化地满足更广泛的分布式应用程序架构。
+
+### 7. arthas
+
+- **Github 地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
+- **star**: 11.6 k (1,199 stars this month)
+- **介绍**: Arthas 是 Alibaba 开源的 Java 诊断工具。
+
+### 8.DoraemonKit
+
+- **Github 地址**:
+- **Star**: 6.2k (1,177 stars this month)
+- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
+
+### 9.elasticsearch
+
+- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
+- **Star**: 39.7k (1,069 stars this month)
+- **介绍**: 开源,分布式,RESTful 搜索引擎。
+
+### 10. tutorials
+
+- **Github 地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
+- **star**: 13 k (998 stars this month)
+- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。
+
diff --git a/docs/github-trending/2019-4.md b/docs/github-trending/2019-4.md
new file mode 100644
index 00000000..713a76da
--- /dev/null
+++ b/docs/github-trending/2019-4.md
@@ -0,0 +1,98 @@
+以下涉及到的数据统计与 2019 年 5 月 1 日 12 点,数据来源: 。
+
+下面的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,比如下面推荐到的开源项目 Hutool 就是近期比较热门的项目之一,它是 Java 工具包,能够帮助我们简化代码!我觉得下面这些项目对于学习 Java 的朋友还是很有帮助的!
+
+
+### 1. JavaGuide
+
+- **Github 地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
+- **Star**: 37.9k (5,660 stars this month)
+- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+
+### 2. advanced-java
+
+- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
+- **Star**: 15.1k (4,654 stars this month)
+- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
+
+### 3. CS-Notes
+
+- **Github 地址**:
+- **Star**: 59.2k (4,012 stars this month)
+- **介绍**: 技术面试必备基础知识。
+
+### 4. ghidra
+
+- **Github 地址**:
+- **Star**: 15.0k (2,995 stars this month)
+- **介绍**: Ghidra是一个软件逆向工程(SRE)框架。
+
+### 5. mall
+
+- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
+- **star**: 11.6 k (2,100 stars this month)
+- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
+
+### 6. ZXBlog
+
+- **Github 地址**:
+- **star**: 2.1 k (2,086 stars this month)
+- **介绍**: 记录各种学习笔记(算法、Java、数据库、并发......)。
+
+### 7.DoraemonKit
+
+- **Github地址**:
+- **Star**: 7.6k (1,541 stars this month)
+- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
+
+### 8. spring-boot
+
+- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
+- **star:** 37.3k (1,489 stars this month)
+- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
+
+**Spring Boot官方的介绍:**
+
+> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
+
+### 9. spring-boot-examples
+
+- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
+- **Star**: 12.8k (1,453 stars this month)
+- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。
+
+### 10. seata
+
+- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
+- **star**: 8.4 k (1441 stars this month)
+- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
+
+### 11. litemall
+
+- **Github 地址**:[https://github.com/ityouknow/spring-boot-examples](https://github.com/ityouknow/spring-boot-examples)
+- **Star**: 6.0k (1,427 stars this month)
+- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
+
+### 12. skywalking
+
+- **Github 地址**:
+- **Star**: 8.0k (1,381 stars this month)
+- **介绍**: 针对分布式系统的应用性能监控,尤其是针对微服务、云原生和面向容器的分布式系统架构。
+
+### 13. elasticsearch
+
+- **Github 地址** [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
+- **Star**: 4.0k (1,068stars this month)
+- **介绍**: 开源,分布式,RESTful 搜索引擎。
+
+### 14. arthas
+
+- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
+- **star**: 12.6 k (1,080 stars this month)
+- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
+
+### 15. hutool
+
+- **Github地址**:
+- **star**: 4.5 k (1,031 stars this month)
+- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。
\ No newline at end of file
diff --git a/docs/github-trending/2019-5.md b/docs/github-trending/2019-5.md
new file mode 100644
index 00000000..df327b79
--- /dev/null
+++ b/docs/github-trending/2019-5.md
@@ -0,0 +1,125 @@
+以下涉及到的数据统计与 2019 年 6 月 1 日 18 点,数据来源: 。下面推荐的内容从 Java 学习文档到最热门的框架再到热门的工具应有尽有,建议收藏+在看!
+
+### 1.LeetCodeAnimation
+
+- **Github 地址**:
+- **Star**: 29.0k (11,492 stars this month)
+- **介绍**: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
+
+### 2.CS-Notes
+
+- **Github 地址**:
+- **Star**: 64.4k (5513 stars this month)
+- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
+
+### 3.JavaGuide
+
+- **Github 地址**:
+- **Star**: 42.0k (4,442 stars this month)
+- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+
+### 4.mall
+
+- **Github 地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall)
+- **star**: 14.6 k (3,086 stars this month)
+- **介绍**: mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。
+
+### 5.advanced-java
+
+- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
+- **Star**: 20.8k (2,394 stars this month)
+- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。
+
+### 6.spring-boot
+
+- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot)
+- **star:** 38.5k (1,339 stars this month)
+- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
+
+**Spring Boot官方的介绍:**
+
+> Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可)
+
+### 7. Java
+
+- **Github 地址**:
+- **Star**:14.3k (1,334 stars this month)
+- **介绍**: All Algorithms implemented in Java。
+
+### 8.server
+
+- **Github 地址**:
+- **star**: 2.2 k (1,275 stars this month)
+- **介绍**: 全开源即时通讯(IM)系统。
+
+### 9.litemall
+
+- **Github 地址**:
+- **Star**: 7.1k (1,114 stars this month)
+- **介绍**: 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
+
+### 10.Linkage-RecyclerView
+
+- **Github 地址**:
+- **Star**: 10.0k (1,093 stars this month)
+- **介绍**: 即使不用饿了么订餐,也请务必收藏好该库!🔥 一行代码即可接入,二级联动订餐列表 - Even if you don't order food by PrubHub, be sure to collect this library, please! 🔥 This secondary linkage list widget can be accessed by only one line of code. Supporting by RecyclerView & AndroidX.
+
+### 11.toBeTopJavaer
+
+- **Github 地址** :
+- **Star**: 3.3k (1,007 stars this month)
+- **介绍**: To Be Top Javaer - Java工程师成神之路
+
+### 12.elasticsearch
+
+- **Github 地址** : [https://github.com/elastic/elasticsearch](https://github.com/elastic/elasticsearch)
+- **Star**: 48.0k (968 stars this month)
+- **介绍**: 开源,分布式,RESTful 搜索引擎。
+
+### 13.java-design-patterns
+
+- **Github 地址** :
+- **Star**: 41.5k (955 stars this month)
+- **介绍**: Design patterns implemented in Java。
+
+### 14.apollo
+
+- **Github 地址** :
+- **Star**: 14.5k (927 stars this month)
+- **介绍**: Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
+
+### 15.arthas
+
+- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
+- **star**: 13.5 k (933 stars this month)
+- **介绍**: Arthas 是Alibaba开源的Java诊断工具。
+
+### 16.dubbo
+
+- **Github地址**:
+- **star**: 26.9 k (769 stars this month)
+- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
+
+### 17.DoraemonKit
+
+- **Github地址**:
+- **Star**: 8.5k (909 stars this month)
+- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。
+
+### 18.halo
+
+- **Github地址**:
+- **Star**: 4.1k (829 stars this month)
+- **介绍**: Halo 可能是最好的 Java 博客系统。
+
+### 19.seata
+
+- **Github 地址** : [https://github.com/seata/seata](https://github.com/seata/seata)
+- **star**: 9.2 k (776 stars this month)
+- **介绍**: Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
+
+### 20.hutool
+
+- **Github地址**:
+- **star**: 5,3 k (812 stars this month)
+- **介绍**: Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。官网: 。
\ No newline at end of file
diff --git a/docs/github-trending/2019-6.md b/docs/github-trending/2019-6.md
new file mode 100644
index 00000000..2a395e16
--- /dev/null
+++ b/docs/github-trending/2019-6.md
@@ -0,0 +1,119 @@
+### 1.CS-Notes
+
+- **Github 地址**:https://github.com/CyC2018/CS-Notes
+- **Star**: 69.8k
+- **介绍**: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
+
+### 2.toBeTopJavaer
+
+- **Github 地址:**[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
+- **Star**: 4.7k
+- **介绍**: To Be Top Javaer - Java工程师成神之路。
+
+### 3.p3c
+
+- **Github 地址:** [https://github.com/alibaba/p3c](https://github.com/alibaba/p3c)
+- **Star**: 16.6k
+- **介绍**: Alibaba Java Coding Guidelines pmd implements and IDE plugin。Eclipse 和 IDEA 上都有该插件,推荐使用!
+
+### 4.SpringCloudLearning
+
+- **Github 地址:** [https://github.com/forezp/SpringCloudLearning](https://github.com/forezp/SpringCloudLearning)
+- **Star**: 8.7k
+- **介绍**: 史上最简单的Spring Cloud教程源码。
+
+### 5.dubbo
+
+- **Github地址**:
+- **star**: 27.6 k
+- **介绍**: Apache Dubbo是一个基于Java的高性能开源RPC框架。
+
+### 6.jeecg-boot
+
+- **Github地址**: [https://github.com/zhangdaiscott/jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)
+- **star**: 3.3 k
+- **介绍**: 一款基于代码生成器的JAVA快速开发平台!全新架构前后端分离:SpringBoot 2.x,Ant Design&Vue,Mybatis,Shiro,JWT。强大的代码生成器让前后端代码一键生成,无需写任何代码,绝对是全栈开发福音!! JeecgBoot的宗旨是提高UI能力的同时,降低前后分离的开发成本,JeecgBoot还独创在线开发模式,No代码概念,一系列在线智能开发:在线配置表单、在线配置报表、在线设计流程等等。
+
+### 7.advanced-java
+
+- **Github 地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
+- **Star**: 24.2k
+- **介绍**: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
+
+### 8.FEBS-Shiro
+
+- **Github 地址**:[https://github.com/wuyouzhuguli/FEBS-Shiro](https://github.com/wuyouzhuguli/FEBS-Shiro)
+- **Star**: 2.6k
+- **介绍**: Spring Boot 2.1.3,Shiro1.4.0 & Layui 2.5.4 权限管理系统。预览地址:http://49.234.20.223:8080/login。
+
+### 9.SpringAll
+
+- **Github 地址**: [https://github.com/wuyouzhuguli/SpringAll](https://github.com/wuyouzhuguli/SpringAll)
+- **Star**: 5.4k
+- **介绍**: 循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。
+
+### 10.JavaGuide
+
+- **Github 地址**:
+- **Star**: 47.2k
+- **介绍**: 【Java 学习+面试指南】 一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+
+### 11.vhr
+
+- **Github 地址**:[https://github.com/lenve/vhr](https://github.com/lenve/vhr)
+- **Star**: 4.9k
+- **介绍**: 微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。
+
+### 12. tutorials
+
+- **Github 地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials)
+- **star**: 15.4 k
+- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。
+
+### 13.EasyScheduler
+
+- **Github 地址**:[https://github.com/analysys/EasyScheduler](https://github.com/analysys/EasyScheduler)
+- **star**: 1.1 k
+- **介绍**: Easy Scheduler是一个分布式工作流任务调度系统,主要解决“复杂任务依赖但无法直接监控任务健康状态”的问题。Easy Scheduler以DAG方式组装任务,可以实时监控任务的运行状态。同时,它支持重试,重新运行等操作... 。https://analysys.github.io/easyscheduler_docs_cn/
+
+### 14.thingsboard
+
+- **Github 地址**:[https://github.com/thingsboard/thingsboard](https://github.com/thingsboard/thingsboard)
+- **star**: 3.7 k
+- **介绍**: 开源物联网平台 - 设备管理,数据收集,处理和可视化。 [https://thingsboard.io](https://thingsboard.io/)
+
+### 15.mall-learning
+
+- **Github 地址**: [https://github.com/macrozheng/mall-learning](https://github.com/macrozheng/mall-learning)
+- **star**: 0.6 k
+- **介绍**: mall学习教程,架构、业务、技术要点全方位解析。mall项目(16k+star)是一套电商系统,使用现阶段主流技术实现。 涵盖了SpringBoot2.1.3、MyBatis3.4.6、Elasticsearch6.2.2、RabbitMQ3.7.15、Redis3.2、Mongodb3.2、Mysql5.7等技术,采用Docker容器化部署。 https://github.com/macrozheng/mall
+
+### 16. flink
+
+- **Github地址**:[https://github.com/apache/flink](https://github.com/apache/flink)
+- **star**: 9.3 k
+- **介绍**: Apache Flink是一个开源流处理框架,具有强大的流和批处理功能。
+
+### 17.spring-cloud-kubernetes
+
+- **Github地址**:[https://github.com/spring-cloud/spring-cloud-kubernetes](https://github.com/spring-cloud/spring-cloud-kubernetes)
+- **star**: 1.4 k
+- **介绍**: Kubernetes 集成 Spring Cloud Discovery Client, Configuration, etc...
+
+### 18.springboot-learning-example
+
+- **Github地址**:[https://github.com/JeffLi1993/springboot-learning-example](https://github.com/JeffLi1993/springboot-learning-example)
+- **star**: 10.0 k
+- **介绍**: spring boot 实践学习案例,是 spring boot 初学者及核心技术巩固的最佳实践。
+
+### 19.canal
+
+- **Github地址**:[https://github.com/alibaba/canal](https://github.com/alibaba/canal)
+- **star**: 9.3 k
+- **介绍**: 阿里巴巴 MySQL binlog 增量订阅&消费组件。
+
+### 20.react-native-device-info
+
+- **Github地址**:[https://github.com/react-native-community/react-native-device-info](https://github.com/react-native-community/react-native-device-info)
+- **star**: 4.0 k
+- **介绍**: React Native iOS和Android的设备信息。
\ No newline at end of file
diff --git a/docs/github-trending/JavaGithubTrending.md b/docs/github-trending/JavaGithubTrending.md
index 06396222..91d544ed 100644
--- a/docs/github-trending/JavaGithubTrending.md
+++ b/docs/github-trending/JavaGithubTrending.md
@@ -1,4 +1,8 @@
- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2018-12.md)
- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-1.md)
- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-2.md)
+- [2019 年 3 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-3.md)
+- [2019 年 4 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-4.md)
+- [2019 年 5 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-5.md)
+- [2019 年 6 月](https://github.com/Snailclimb/JavaGuide/blob/master/docs/github-trending/2019-6.md)
diff --git a/docs/index.html b/docs/index.html
deleted file mode 100644
index f6ebaf23..00000000
--- a/docs/index.html
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
- JavaGuide
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/java/BIO-NIO-AIO.md b/docs/java/BIO-NIO-AIO.md
index c5ec6ddd..caeeeb89 100644
--- a/docs/java/BIO-NIO-AIO.md
+++ b/docs/java/BIO-NIO-AIO.md
@@ -30,20 +30,23 @@
在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。
-**同步与异步**
+关于同步和异步的概念解读困扰着很多程序员,大部分的解读都会带有自己的一点偏见。参考了 [Stackoverflow](https://stackoverflow.com/questions/748175/asynchronous-vs-synchronous-execution-what-does-it-really-mean)相关问题后对原有答案进行了进一步完善:
-- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
-- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
+> When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.
+>
+> 当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。
-同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
+- **同步** :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在`A->B`事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用种被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。
+- **异步**: 两个异步的任务完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用种一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情,
**阻塞和非阻塞**
- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
-举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。
+**如何区分 “同步/异步 ”和 “阻塞/非阻塞” 呢?**
+同步/异步是从行为角度描述事物的,而阻塞和非阻塞描述的当前事物的状态(等待调用结果时的状态)。
## 1. BIO (Blocking I/O)
@@ -73,7 +76,7 @@ BIO通信(一请求一应答)模型图如下(图源网络,原出处不明)
采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
-伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。
+伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。
### 1.3 代码示例
@@ -164,14 +167,12 @@ public class IOServer {
在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
-
-
## 2. NIO (New I/O)
### 2.1 NIO 简介
- NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
-
+ NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
+
NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
### 2.2 NIO的特性/NIO与IO区别
@@ -202,13 +203,13 @@ NIO 通过Channel(通道) 进行读写。
通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
-#### 4)Selectors(选择器)
+#### 4)Selector (选择器)
NIO有选择器,而IO没有。
选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
-
+
### 2.3 NIO 读数据和写数据方式
通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
@@ -273,8 +274,7 @@ public class NIOServer {
if (key.isAcceptable()) {
try {
- // (1)
- // 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
+ // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
clientChannel.register(clientSelector, SelectionKey.OP_READ);
diff --git a/docs/java/J2EE基础知识.md b/docs/java/J2EE基础知识.md
index ced017ab..22ce6911 100644
--- a/docs/java/J2EE基础知识.md
+++ b/docs/java/J2EE基础知识.md
@@ -1,3 +1,5 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
- [Servlet总结](#servlet总结)
@@ -26,7 +28,7 @@
## Servlet总结
-在Java Web程序中,**Servlet**主要负责接收用户请求**HttpServletRequest**,在**doGet()**,**doPost()**中做相应的处理,并将回应**HttpServletResponse**反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用**init()方法**,销毁时调用**destroy()方法**。**Servlet需要在web.xml中配置**(MyEclipse中创建Servlet会自动配置),**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。
+在Java Web程序中,**Servlet**主要负责接收用户请求 `HttpServletRequest`,在`doGet()`,`doPost()`中做相应的处理,并将回应`HttpServletResponse`反馈给用户。**Servlet** 可以设置初始化参数,供Servlet内部使用。一个Servlet类只会有一个实例,在它初始化时调用`init()`方法,销毁时调用`destroy()`方法**。**Servlet需要在web.xml中配置(MyEclipse中创建Servlet会自动配置),**一个Servlet可以设置多个URL访问**。**Servlet不是线程安全**,因此要谨慎使用类变量。
## 阐述Servlet和CGI的区别?
@@ -55,11 +57,11 @@
## Servlet接口中有哪些方法及Servlet生命周期探秘
Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期相关**:
-- **void init(ServletConfig config) throws ServletException**
-- **void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException**
-- **void destory()**
-- java.lang.String getServletInfo()
-- ServletConfig getServletConfig()
+- `void init(ServletConfig config) throws ServletException`
+- `void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException`
+- `void destroy()`
+- `java.lang.String getServletInfo()`
+- `ServletConfig getServletConfig()`
**生命周期:** **Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。
@@ -67,21 +69,11 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期
## get和post请求的区别
-> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))!我下面给出的只是一种常见的答案。
+get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))!
-①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;
+可以把 get 和 post 当作两个不同的行为,两者并没有什么本质区别,底层都是 TCP 连接。 get请求用来从服务器上获得资源,而post是用来向服务器提交数据。比如你要获取人员列表可以用 get 请求,你需要创建一个人员可以用 post 。这也是 Restful API 最基本的一个要求。
-②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
-
-③get传输的数据要受到URL长度限制(最大长度是 2048 个字符);而post可以传输大量的数据,上传文件通常要使用post方式;
-
-④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post;
-
-⑤get使用MIME类型application/x-www-form-urlencoded的URL编码(也叫百分号编码)文本的格式传递参数,保证被传送的参数由遵循规范的文本组成,例如一个空格的编码是"%20"。
-
-补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。
-
-还有另外一种回答。推荐大家看一下:
+推荐阅读:
- https://www.zhihu.com/question/28586791
- https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd
@@ -93,7 +85,7 @@ Form标签里的method的属性为get时调用doGet(),为post时调用doPost()
**转发是服务器行为,重定向是客户端行为。**
-**转发(Forword)**
+**转发(Forward)**
通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。
```java
request.getRequestDispatcher("login_success.jsp").forward(request, response);
@@ -143,13 +135,11 @@ Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm");
JSP是一种Servlet,但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下,为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类(接口Servlet的一个子类)。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。
工程JspLoginDemo下有一个名为login.jsp的Jsp文件,把工程第一次部署到服务器上后访问这个Jsp文件,我们发现这个目录下多了下图这两个东东。
.class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候,Tomcat将不再重新编译JSP文件,而是直接调用class文件来响应客户端请求。
-
+
由于JSP只会在客户端第一次请求的时候被编译 ,因此第一次请求JSP时会感觉比较慢,之后就会感觉快很多。如果把服务器保存的class文件删除,服务器也会重新编译JSP。
开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP,而不需要重启Tomcat。这种自动检测功能是默认开启的,检测改动会消耗少量的时间,在部署Web应用的时候可以在web.xml中将它关掉。
-
-
参考:《javaweb整合开发王者归来》P97
## JSP有哪些内置对象、作用分别是什么
@@ -195,31 +185,31 @@ JSP有9个内置对象:
## request.getAttribute()和 request.getParameter()有何区别
**从获取方向来看:**
-getParameter()是获取 POST/GET 传递的参数值;
+`getParameter()`是获取 POST/GET 传递的参数值;
-getAttribute()是获取对象容器中的数据值;
+`getAttribute()`是获取对象容器中的数据值;
**从用途来看:**
-getParameter用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。
+`getParameter()`用于客户端重定向时,即点击了链接或提交按扭时传值用,即用于在用表单或url重定向传值时接收数据用。
-getAttribute用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了
+`getAttribute()` 用于服务器端重定向时,即在 sevlet 中使用了 forward 函数,或 struts 中使用了
mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。
-另外,可以用 setAttribute,getAttribute 发送接收对象.而 getParameter 显然只能传字符串。
-setAttribute 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样getAttribute就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。getParameter只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。
+另外,可以用 `setAttribute()`,`getAttribute()` 发送接收对象.而 `getParameter()` 显然只能传字符串。
+`setAttribute()` 是应用服务器把这个对象放在该页面所对应的一块内存中去,当你的页面服务器重定向到另一个页面时,应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样`getAttribute()`就能取得你所设下的值,当然这种方法可以传对象。session也一样,只是对象在内存中的生命周期不一样而已。`getParameter()`只是应用服务器在分析你送上来的 request页面的文本时,取得你设在表单或 url 重定向时的值。
**总结:**
-getParameter 返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等)
+`getParameter()`返回的是String,用于读取提交的表单中的值;(获取之后会根据实际需要转换为自己需要的相应类型,比如整型,日期类型啊等等)
-getAttribute 返回的是Object,需进行转换,可用setAttribute 设置成任意对象,使用很灵活,可随时用
+`getAttribute()`返回的是Object,需进行转换,可用`setAttribute()`设置成任意对象,使用很灵活,可随时用
## include指令include的行为的区别
**include指令:** JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分,会被同时编译执行。 语法格式如下:
<%@ include file="文件相对 url 地址" %>
-i**nclude动作:** 动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下:
+i**nclude动作:** ``动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下:
## JSP九大内置对象,七大动作,三大指令
@@ -232,11 +222,9 @@ JSP中的四种作用域包括page、request、session和application,具体来
- **session**代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- **application**代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。
-
-
## 如何实现JSP或Servlet的单线程模式
对于JSP页面,可以通过page指令进行设置。
-<%@page isThreadSafe=”false”%>
+`<%@page isThreadSafe="false"%>`
对于Servlet,可以让自定义的Servlet实现SingleThreadModel标识接口。
@@ -294,12 +282,20 @@ if(cookies !=null){
在所有会话跟踪技术中,HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession,通过HttpSession的setAttribute方法可以将一个值放在HttpSession中,通过调用 HttpSession对象的getAttribute方法,同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是,HttpSession放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象,这个对象最好实现了 Serializable接口,这样Servlet容器在必要的时候可以将其序列化到文件中,否则在序列化时就会出现异常。
## Cookie和Session的的区别
-1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
-2. 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
-3. Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
+Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
-参考:
+ **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
-https://www.zhihu.com/question/19786827/answer/28752144
+Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
-《javaweb整合开发王者归来》P158 Cookie和Session的比较
+Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/JAD反编译tricks.md b/docs/java/JAD反编译tricks.md
new file mode 100644
index 00000000..8a0a80de
--- /dev/null
+++ b/docs/java/JAD反编译tricks.md
@@ -0,0 +1,375 @@
+[jad](https://varaneckas.com/jad/)反编译工具,已经不再更新,且只支持JDK1.4,但并不影响其强大的功能。
+
+基本用法:`jad xxx.class`,会生成直接可读的xxx.jad文件。
+
+## 自动拆装箱
+
+对于基本类型和包装类型之间的转换,通过xxxValue()和valueOf()两个方法完成自动拆装箱,使用jad进行反编译可以看到该过程:
+
+```java
+public class Demo {
+ public static void main(String[] args) {
+ int x = new Integer(10); // 自动拆箱
+ Integer y = x; // 自动装箱
+ }
+}
+```
+反编译后结果:
+
+```java
+public class Demo
+{
+ public Demo(){}
+
+ public static void main(String args[])
+ {
+ int i = (new Integer(10)).intValue(); // intValue()拆箱
+ Integer integer = Integer.valueOf(i); // valueOf()装箱
+ }
+}
+```
+
+
+
+## foreach语法糖
+
+在遍历迭代时可以foreach语法糖,对于数组类型直接转换成for循环:
+
+```java
+// 原始代码
+int[] arr = {1, 2, 3, 4, 5};
+ for(int item: arr) {
+ System.out.println(item);
+ }
+}
+
+// 反编译后代码
+int ai[] = {
+ 1, 2, 3, 4, 5
+};
+int ai1[] = ai;
+int i = ai1.length;
+// 转换成for循环
+for(int j = 0; j < i; j++)
+{
+ int k = ai1[j];
+ System.out.println(k);
+}
+```
+
+
+
+对于容器类的遍历会使用iterator进行迭代:
+
+```java
+import java.io.PrintStream;
+import java.util.*;
+
+public class Demo
+{
+ public Demo() {}
+ public static void main(String args[])
+ {
+ ArrayList arraylist = new ArrayList();
+ arraylist.add(Integer.valueOf(1));
+ arraylist.add(Integer.valueOf(2));
+ arraylist.add(Integer.valueOf(3));
+ Integer integer;
+ // 使用的for循环+Iterator,类似于链表迭代:
+ // for (ListNode cur = head; cur != null; System.out.println(cur.val)){
+ // cur = cur.next;
+ // }
+ for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(integer))
+ integer = (Integer)iterator.next();
+ }
+}
+```
+
+
+
+## Arrays.asList(T...)
+
+熟悉Arrays.asList(T...)用法的小伙伴都应该知道,asList()方法传入的参数不能是基本类型的数组,必须包装成包装类型再使用,否则对应生成的列表的大小永远是1:
+
+```java
+import java.util.*;
+public class Demo {
+ public static void main(String[] args) {
+ int[] arr1 = {1, 2, 3};
+ Integer[] arr2 = {1, 2, 3};
+ List lists1 = Arrays.asList(arr1);
+ List lists2 = Arrays.asList(arr2);
+ System.out.println(lists1.size()); // 1
+ System.out.println(lists2.size()); // 3
+ }
+}
+```
+
+从反编译结果来解释,为什么传入基本类型的数组后,返回的List大小是1:
+
+```java
+// 反编译后文件
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
+
+public class Demo
+{
+ public Demo() {}
+
+ public static void main(String args[])
+ {
+ int ai[] = {
+ 1, 2, 3
+ };
+ // 使用包装类型,全部元素由int包装为Integer
+ Integer ainteger[] = {
+ Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)
+ };
+
+ // 注意这里被反编译成二维数组,而且是一个1行三列的二维数组
+ // list.size()当然返回1
+ List list = Arrays.asList(new int[][] { ai });
+ List list1 = Arrays.asList(ainteger);
+ System.out.println(list.size());
+ System.out.println(list1.size());
+ }
+}
+```
+
+从上面结果可以看到,传入基本类型的数组后,会被转换成一个二维数组,而且是**new int\[1]\[arr.length]**这样的数组,调用list.size()当然返回1。
+
+
+
+## 注解
+
+Java中的类、接口、枚举、注解都可以看做是类类型。使用jad来看一下@interface被转换成什么:
+
+```java
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Foo{
+ String[] value();
+ boolean bar();
+}
+```
+查看反编译代码可以看出:
+
+- 自定义的注解类Foo被转换成接口Foo,并且继承Annotation接口
+- 原来自定义接口中的value()和bar()被转换成抽象方法
+
+```java
+import java.lang.annotation.Annotation;
+
+public interface Foo
+ extends Annotation
+{
+ public abstract String[] value();
+
+ public abstract boolean bar();
+}
+```
+注解通常和反射配合使用,而且既然自定义的注解最终被转换成接口,注解中的属性被转换成接口中的抽象方法,那么通过反射之后拿到接口实例,在通过接口实例自然能够调用对应的抽象方法:
+```java
+import java.util.Arrays;
+
+@Foo(value={"sherman", "decompiler"}, bar=true)
+public class Demo{
+ public static void main(String[] args) {
+ Foo foo = Demo.class.getAnnotation(Foo.class);
+ System.out.println(Arrays.toString(foo.value())); // [sherman, decompiler]
+ System.out.println(foo.bar()); // true
+ }
+}
+```
+
+
+## 枚举
+
+通过jad反编译可以很好地理解枚举类。
+
+
+
+### 空枚举
+
+先定义一个空的枚举类:
+
+```java
+public enum DummyEnum {
+}
+```
+使用jad反编译查看结果:
+
+- 自定义枚举类被转换成final类,并且继承Enum
+- 提供了两个参数(name,odinal)的私有构造器,并且调用了父类的构造器。注意即使没有提供任何参数,也会有该该构造器,其中name就是枚举实例的名称,odinal是枚举实例的索引号
+- 初始化了一个private static final自定义类型的空数组 **$VALUES**
+- 提供了两个public static方法:
+ - values()方法通过clone()方法返回内部$VALUES的浅拷贝。这个方法结合私有构造器可以完美实现单例模式,想一想values()方法是不是和单例模式中getInstance()方法功能类似
+ - valueOf(String s):调用父类Enum的valueOf方法并强转返回
+
+```java
+public final class DummyEnum extends Enum
+{
+ // 功能和单例模式的getInstance()方法相同
+ public static DummyEnum[] values()
+ {
+ return (DummyEnum[])$VALUES.clone();
+ }
+ // 调用父类的valueOf方法,并墙砖返回
+ public static DummyEnum valueOf(String s)
+ {
+ return (DummyEnum)Enum.valueOf(DummyEnum, s);
+ }
+ // 默认提供一个私有的私有两个参数的构造器,并调用父类Enum的构造器
+ private DummyEnum(String s, int i)
+ {
+ super(s, i);
+ }
+ // 初始化一个private static final的本类空数组
+ private static final DummyEnum $VALUES[] = new DummyEnum[0];
+
+}
+
+```
+### 包含抽象方法的枚举
+
+枚举类中也可以包含抽象方法,但是必须定义枚举实例并且立即重写抽象方法,就像下面这样:
+
+```java
+public enum DummyEnum {
+ DUMMY1 {
+ public void dummyMethod() {
+ System.out.println("[1]: implements abstract method in enum class");
+ }
+ },
+
+ DUMMY2 {
+ public void dummyMethod() {
+ System.out.println("[2]: implements abstract method in enum class");
+ }
+ };
+
+ abstract void dummyMethod();
+
+}
+```
+再来反编译看看有哪些变化:
+
+- 原来final class变成了abstract class:这很好理解,有抽象方法的类自然是抽象类
+- 多了两个public static final的成员DUMMY1、DUMMY2,这两个实例的初始化过程被放到了static代码块中,并且实例过程中直接重写了抽象方法,类似于匿名内部类的形式。
+- 数组**$VALUES[]**初始化时放入枚举实例
+
+还有其它变化么?
+
+在反编译后的DummyEnum类中,是存在抽象方法的,而枚举实例在静态代码块中初始化过程中重写了抽象方法。在Java中,抽象方法和抽象方法重写同时放在一个类中,只能通过内部类形式完成。因此上面第二点应该说成就是以内部类形式初始化。
+
+可以看一下DummyEnum.class存放的位置,应该多了两个文件:
+
+- DummyEnum$1.class
+- DummyEnum$2.class
+
+Java中.class文件出现$符号表示有内部类存在,就像OutClass$InnerClass,这两个文件出现也应证了上面的匿名内部类初始化的说法。
+
+```java
+import java.io.PrintStream;
+
+public abstract class DummyEnum extends Enum
+{
+ public static DummyEnum[] values()
+ {
+ return (DummyEnum[])$VALUES.clone();
+ }
+
+ public static DummyEnum valueOf(String s)
+ {
+ return (DummyEnum)Enum.valueOf(DummyEnum, s);
+ }
+
+ private DummyEnum(String s, int i)
+ {
+ super(s, i);
+ }
+
+ // 抽象方法
+ abstract void dummyMethod();
+
+ // 两个pubic static final实例
+ public static final DummyEnum DUMMY1;
+ public static final DummyEnum DUMMY2;
+ private static final DummyEnum $VALUES[];
+
+ // static代码块进行初始化
+ static
+ {
+ DUMMY1 = new DummyEnum("DUMMY1", 0) {
+ public void dummyMethod()
+ {
+ System.out.println("[1]: implements abstract method in enum class");
+ }
+ }
+;
+ DUMMY2 = new DummyEnum("DUMMY2", 1) {
+ public void dummyMethod()
+ {
+ System.out.println("[2]: implements abstract method in enum class");
+ }
+ }
+;
+ // 对本类数组进行初始化
+ $VALUES = (new DummyEnum[] {
+ DUMMY1, DUMMY2
+ });
+ }
+}
+```
+
+
+
+### 正常的枚举类
+
+实际开发中,枚举类通常的形式是有两个参数(int code,Sring msg)的构造器,可以作为状态码进行返回。Enum类实际上也是提供了包含两个参数且是protected的构造器,这里为了避免歧义,将枚举类的构造器设置为三个,使用jad反编译:
+
+最大的变化是:现在的private构造器从2个参数变成5个,而且在内部仍然将前两个参数通过super传递给父类,剩余的三个参数才是真正自己提供的参数。可以想象,如果自定义的枚举类只提供了一个参数,最终生成底层代码中private构造器应该有三个参数,前两个依然通过super传递给父类。
+
+```java
+public final class CustomEnum extends Enum
+{
+ public static CustomEnum[] values()
+ {
+ return (CustomEnum[])$VALUES.clone();
+ }
+
+ public static CustomEnum valueOf(String s)
+ {
+ return (CustomEnum)Enum.valueOf(CustomEnum, s);
+ }
+
+ private CustomEnum(String s, int i, int j, String s1, Object obj)
+ {
+ super(s, i);
+ code = j;
+ msg = s1;
+ data = obj;
+ }
+
+ public static final CustomEnum FIRST;
+ public static final CustomEnum SECOND;
+ public static final CustomEnum THIRD;
+ private int code;
+ private String msg;
+ private Object data;
+ private static final CustomEnum $VALUES[];
+
+ static
+ {
+ FIRST = new CustomEnum("FIRST", 0, 10010, "first", Long.valueOf(100L));
+ SECOND = new CustomEnum("SECOND", 1, 10020, "second", "Foo");
+ THIRD = new CustomEnum("THIRD", 2, 10030, "third", new Object());
+ $VALUES = (new CustomEnum[] {
+ FIRST, SECOND, THIRD
+ });
+ }
+}
+```
diff --git a/docs/java/Java IO与NIO.md b/docs/java/Java IO与NIO.md
index 905df527..74bd850e 100644
--- a/docs/java/Java IO与NIO.md
+++ b/docs/java/Java IO与NIO.md
@@ -27,12 +27,12 @@
**(1) 按操作方式分类结构图:**
-
+
**(2)按操作对象分类结构图**
-
+
### [二 java IO体系的学习总结](https://blog.csdn.net/nightcurtis/article/details/51324105)
1. **IO流的分类:**
@@ -92,7 +92,7 @@
- 写入数据到缓冲区(Writing Data to a Buffer)
**写数据到Buffer有两种方法:**
-
+
1.从Channel中写数据到Buffer
```java
int bytesRead = inChannel.read(buf); //read into buffer.
@@ -103,7 +103,7 @@
```
4. **Buffer常用方法测试**
-
+
说实话,NIO编程真的难,通过后面这个测试例子,你可能才能勉强理解前面说的Buffer方法的作用。
diff --git a/docs/java/Java基础知识.md b/docs/java/Java基础知识.md
index c7d57e34..af0d2c9c 100644
--- a/docs/java/Java基础知识.md
+++ b/docs/java/Java基础知识.md
@@ -1,73 +1,76 @@
-
-
-
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。
+
- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别)
- - [面向过程](#面向过程)
- - [面向对象](#面向对象)
-- [2. Java 语言有哪些特点](#2-java-语言有哪些特点)
+- [2. Java 语言有哪些特点?](#2-java-语言有哪些特点)
- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答)
- - [JVM](#jvm)
- - [JDK 和 JRE](#jdk-和-jre)
+ - [JVM](#jvm)
+ - [JDK 和 JRE](#jdk-和-jre)
- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比)
-- [5. Java和C++的区别](#5-java和c的区别)
-- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同)
-- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别)
-- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别)
-- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override)
+- [5. Java 和 C++的区别?](#5-java-和-c的区别)
+- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同)
+- [7. Java 应用程序与小程序之间有哪些差别?](#7-java-应用程序与小程序之间有哪些差别)
+- [8. 字符型常量和字符串常量的区别?](#8-字符型常量和字符串常量的区别)
+- [9. 构造器 Constructor 是否可被 override?](#9-构造器-constructor-是否可被-override)
- [10. 重载和重写的区别](#10-重载和重写的区别)
+ - [重载](#重载)
+ - [重写](#重写)
- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态)
- - [封装](#封装)
- - [继承](#继承)
- - [多态](#多态)
-- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的)
+ - [封装](#封装)
+ - [继承](#继承)
+ - [多态](#多态)
+- [12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的)
- [13. 自动装箱与拆箱](#13-自动装箱与拆箱)
-- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的)
+- [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的)
- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用)
-- [16. import java和javax有什么区别](#16-import-java和javax有什么区别)
-- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么)
-- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些)
+- [16. import java 和 javax 有什么区别?](#16-import-java-和-javax-有什么区别)
+- [17. 接口和抽象类的区别是什么?](#17-接口和抽象类的区别是什么)
+- [18. 成员变量与局部变量的区别有哪些?](#18-成员变量与局部变量的区别有哪些)
- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同)
- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么)
-- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么)
-- [22. 构造方法有哪些特性](#22-构造方法有哪些特性)
+- [21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么)
+- [22. 构造方法有哪些特性?](#22-构造方法有哪些特性)
- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同)
-- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同)
-- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是)
-- [26. == 与 equals\(重要\)](#26--与-equals重要)
-- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要)
- - [hashCode()介绍](#hashcode()介绍)
- - [为什么要有 hashCode](#为什么要有-hashcode)
- - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定)
-- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递)
-- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么)
+- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同)
+- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是)
+- [26. == 与 equals(重要)](#26--与-equals重要)
+- [27. hashCode 与 equals (重要)](#27-hashcode-与-equals-重要)
+ - [hashCode()介绍](#hashcode介绍)
+ - [为什么要有 hashCode](#为什么要有-hashcode)
+ - [hashCode()与 equals()的相关规定](#hashcode与-equals的相关规定)
+- [28. 为什么 Java 中只有值传递?](#28-为什么-java-中只有值传递)
+- [29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程程序进程的基本概念以及他们之间关系是什么)
- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态)
- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结)
- [32 Java 中的异常处理](#32-java-中的异常处理)
- - [Java异常类层次结构图](#java异常类层次结构图)
- - [Throwable类常用方法](#throwable类常用方法)
- - [异常处理总结](#异常处理总结)
-- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办)
-- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法)
+ - [Java 异常类层次结构图](#java-异常类层次结构图)
+ - [Throwable 类常用方法](#throwable-类常用方法)
+ - [异常处理总结](#异常处理总结)
+- [33 Java 序列化中如果有些字段不想进行序列化,怎么办?](#33-java-序列化中如果有些字段不想进行序列化怎么办)
+- [34 获取用键盘输入常用的两种方法](#34-获取用键盘输入常用的两种方法)
+- [35 Java 中 IO 流](#35-java-中-io-流)
+ - [Java 中 IO 流分为几种?](#java-中-io-流分为几种)
+ - [既然有了字节流,为什么还要有字符流?](#既然有了字节流为什么还要有字符流)
+ - [BIO,NIO,AIO 有什么区别?](#bionioaio-有什么区别)
+- [36. 常见关键字总结:static,final,this,super](#36-常见关键字总结staticfinalthissuper)
+- [37. Collections 工具类和 Arrays 工具类常见方法总结](#37-collections-工具类和-arrays-工具类常见方法总结)
+- [38. 深拷贝 vs 浅拷贝](#38-深拷贝-vs-浅拷贝)
- [参考](#参考)
+- [公众号](#公众号)
-
-
+
## 1. 面向对象和面向过程的区别
-### 面向过程
+- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。**
+- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。
-**优点:** 性能比面向对象高。因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发
+参见 issue : [面向过程 :面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431)
-**缺点:** 没有面向对象易维护、易复用、易扩展
-
-### 面向对象
-
-**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
-
-**缺点:** 性能比面向过程低
+> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。
+>
+> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。
## 2. Java 语言有哪些特点?
@@ -80,89 +83,124 @@
7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
8. 编译与解释并存;
+> 修正(参见: [issue#544](https://github.com/Snailclimb/JavaGuide/issues/544)):C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
+
## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答
### JVM
-Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
**什么是字节码?采用字节码的好处是什么?**
-> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。
+> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
-**Java 程序从源代码到运行一般有下面3步:**
+**Java 程序从源代码到运行一般有下面 3 步:**

-我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
+我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
-> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
+> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
-总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
+**总结:**
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
### JDK 和 JRE
-JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
+JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
-JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。
+JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
-如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
+如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
## 4. Oracle JDK 和 OpenJDK 的对比
-可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
+可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
-对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案:
+对于 Java 7,没什么关键的地方。OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:
-> 问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别?
+> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
>
-> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。
+> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
-总结:
+**总结:**
-1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
-2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
-3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
-4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
-5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
-6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
+1. Oracle JDK 大概每 6 个月发一次主要版本,而 OpenJDK 版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:[https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence](https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence) 。
+2. OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的;
+3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
+4. 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能;
+5. Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
+6. Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
-## 5. Java和C++的区别?
+## 5. Java 和 C++的区别?
-我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来!
+我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过 C++,也要记下来!
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
- Java 有自动内存管理机制,不需要程序员手动释放无用内存
-
+- **在 C 语言中,字符串或字符数组最后都会有一个额外的字符‘\0’来表示结束。但是,Java 语言中没有结束符这一概念。** 这是一个值得深度思考的问题,具体原因推荐看这篇文章: [https://blog.csdn.net/sszgg2006/article/details/49148189](https://blog.csdn.net/sszgg2006/article/details/49148189)
## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?
一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。
-## 7. Java 应用程序与小程序之间有那些差别?
+## 7. Java 应用程序与小程序之间有哪些差别?
-简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。
+简单说应用程序是从主线程启动(也就是 `main()` 方法)。applet 小程序没有 `main()` 方法,主要是嵌在浏览器页面上运行(调用`init()`或者`run()`来启动),嵌入浏览器这点跟 flash 的小游戏类似。
## 8. 字符型常量和字符串常量的区别?
1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符
-2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
-3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**)
+2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
+3. 占内存大小 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**)
-> java编程思想第四版:2.2.2节
-
+> java 编程思想第四版:2.2.2 节
+> 
## 9. 构造器 Constructor 是否可被 override?
-在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
+Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
## 10. 重载和重写的区别
-**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
+> 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
+>
+> 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
-**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
+#### 重载
+
+发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
+
+下面是《Java 核心技术》对重载这个概念的介绍:
+
+
+
+**综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。**
+
+#### 重写
+
+重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
+
+1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
+2. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
+3. 构造方法无法被重写
+
+**综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变**
+
+**暖心的 Guide 哥最后再来个图标总结一下!**
+
+| 区别点 | 重载方法 | 重写方法 |
+| :--------- | :------- | :--------------------------------------------- |
+| 发生范围 | 同一个类 | 子类 中 |
+| 参数列表 | 必须修改 | 一定不能修改 |
+| 返回类型 | 可修改 | 一定不能修改 |
+| 异常 | 可修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
+| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
+| 发生阶段 | 编译期 | 运行期 |
## 11. Java 面向对象编程三大特性: 封装 继承 多态
@@ -170,13 +208,13 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
-
### 继承
+
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
**关于继承如下 3 点请记住:**
-1. 子类拥有父类非 private 的属性和方法。
+1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
@@ -184,83 +222,103 @@ JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
-在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
+在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
## 12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
**可变性**
-
-简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
+简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。
+
+> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 `private final byte[] value`;
+
+而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
-AbstractStringBuilder.java
+`AbstractStringBuilder.java`
```java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
+ /**
+ * The value is used for character storage.
+ */
char[] value;
+
+ /**
+ * The count is the number of characters used.
+ */
int count;
- AbstractStringBuilder() {
- }
+
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
```
-
**线程安全性**
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
**性能**
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-**对于三者使用的总结:**
-1. 操作少量的数据: 适用String
-2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder
-3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer
+**对于三者使用的总结:**
+
+1. 操作少量的数据: 适用 String
+2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
+3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
## 13. 自动装箱与拆箱
-**装箱**:将基本类型用它们对应的引用类型包装起来;
-**拆箱**:将包装类型转换为基本数据类型;
+- **装箱**:将基本类型用它们对应的引用类型包装起来;
+- **拆箱**:将包装类型转换为基本数据类型;
+
+更多内容见:[深入剖析 Java 中的装箱和拆箱](https://www.cnblogs.com/dolphin0520/p/3780005.html)
## 14. 在一个静态方法内调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用
- Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
-
-## 16. import java和javax有什么区别?
-刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
+Java 程序在执行子类的构造方法之前,如果没有用 `super()`来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 `super()`来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
-所以,实际上java和javax没有区别。这都是一个名字。
+## 16. import java 和 javax 有什么区别?
+
+刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
+
+所以,实际上 java 和 javax 没有区别。这都是一个名字。
## 17. 接口和抽象类的区别是什么?
1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
-2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定。
-3. 一个类可以实现多个接口,但最多只能实现一个抽象类。
-4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
-5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象。从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
+2. 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。
+3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
+4. 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。
+5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。
-备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146))
+> 备注:
+>
+> 1. 在 JDK8 中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。(详见 issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)。
+> 2. jdk9 的接口被允许定义私有方法 。
-## 18. 成员变量与局部变量的区别有那些?
+总结一下 jdk7~jdk9 Java 中接口概念的变化([相关阅读](https://www.geeksforgeeks.org/private-methods-java-9-interfaces/)):
+
+1. 在 jdk 7 或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。
+2. jdk8 的时候接口可以有默认方法和静态方法功能。
+3. Jdk 9 在接口中引入了私有方法和私有静态方法。
+
+## 18. 成员变量与局部变量的区别有哪些?
1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
-2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
+2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值),而局部变量则不会自动赋值。
+4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
-new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。
+new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么?
@@ -273,12 +331,12 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象
## 22. 构造方法有哪些特性?
1. 名字与类名相同。
-2. 没有返回值,但不能用void声明构造函数。
+2. 没有返回值,但不能用 void 声明构造函数。
3. 生成类的对象时自动执行,无需调用。
## 23. 静态方法和实例方法有何不同
-1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
+1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
@@ -295,9 +353,9 @@ new运算符,new创建对象实例(对象实例在堆内存中),对象
**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。
**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
+- 情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
+- 情况 2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
**举个例子:**
@@ -322,138 +380,149 @@ public class test1 {
```
**说明:**
+
- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
+## 27. hashCode 与 equals (重要)
-
-## 27. hashCode 与 equals (重要)
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
+面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
### hashCode()介绍
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
+
+hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
### 为什么要有 hashCode
+**我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
+通过我们可以看出:`hashCode()` 的作用就是**获取哈希码**,也称为散列码;它实际上是返回一个 int 整数。这个**哈希码的作用**是确定该对象在哈希表中的索引位置。**`hashCode()`在散列表中才有用,在其它情况下没用**。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
-当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
+### hashCode()与 equals()的相关规定
-
-
-### hashCode()与equals()的相关规定
-
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个对象分别调用equals方法都返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
+1. 如果两个对象相等,则 hashcode 一定也是相同的
+2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
+3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
+推荐阅读:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
-## 28. 为什么Java中只有值传递?
-
- [为什么Java中只有值传递?](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)
+## 28. 为什么 Java 中只有值传递?
+[为什么 Java 中只有值传递?](https://juejin.im/post/5e18879e6fb9a02fc63602e2)
## 29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
-**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
+**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
-**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
+**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
## 30. 线程有哪些基本状态?
-Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。
+Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。

-线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节):
+线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):

-
-
由上图可以看出:
线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
-> 操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
+> 操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。

-当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。
+当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的`run()`方法之后将会进入到 **TERMINATED(终止)** 状态。
## 31 关于 final 关键字的一些总结
-final关键字主要用在三个地方:变量、方法、类。
+final 关键字主要用在三个地方:变量、方法、类。
-1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
-2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
-3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
+1. 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
+2. 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。
+3. 使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
## 32 Java 中的异常处理
-### Java异常类层次结构图
+### Java 异常类层次结构图
-
- 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
+
-**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
+图片来自:https://simplesnippets.tech/exception-handling-in-java-part-1/
-这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
-**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。
+
-**注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。**
+图片来自:https://chercher.tech/java-programming/exceptions-java
-### Throwable类常用方法
-- **public string getMessage()**:返回异常发生时的详细信息
-- **public string toString()**:返回异常发生时的简要描述
-- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
-- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息
+在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 **Throwable 类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
+
+**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
+
+这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
+
+**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由 Java 虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以 0 时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。
+
+**注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。**
+
+### Throwable 类常用方法
+
+- **public string getMessage()**:返回异常发生时的简要描述
+- **public string toString()**:返回异常发生时的详细信息
+- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
+- **public void printStackTrace()**:在控制台上打印 Throwable 对象封装的异常信息
### 异常处理总结
-- **try 块:**用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
-- **catch 块:**用于处理try捕获到的异常。
-- **finally 块:**无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
+- **try 块:** 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
+- **catch 块:** 用于处理 try 捕获到的异常。
+- **finally 块:** 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return
+ 语句时,finally 语句块将在方法返回之前被执行。
-**在以下4种特殊情况下,finally块不会被执行:**
+**在以下 4 种特殊情况下,finally 块不会被执行:**
-1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行
-2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行
+1. 在 finally 语句块第一行发生了异常。 因为在其他行,finally 块还是会得到执行
+2. 在前面的代码中用了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后,finally 会执行
3. 程序所在的线程死亡。
-4. 关闭CPU。
+4. 关闭 CPU。
-下面这部分内容来自issue:。
+下面这部分内容来自 issue:。
-**关于返回值:**
+**注意:** 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
-如果try语句里有return,返回的是try语句块中变量值。
-详细执行过程如下:
+```java
+ public static int f(int value) {
+ try {
+ return value * value;
+ } finally {
+ if (value == 2) {
+ return 0;
+ }
+ }
+ }
+```
-1. 如果有返回值,就把返回值保存到局部变量中;
-2. 执行jsr指令跳到finally语句里执行;
-3. 执行完finally语句后,返回之前保存在局部变量表里的值。
-4. 如果try,finally语句里均有return,忽略try的return,而使用finally的return.
+如果调用 `f(2)`,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。
-## 33 Java序列化中如果有些字段不想进行序列化,怎么办?
+## 33 Java 序列化中如果有些字段不想进行序列化,怎么办?
-对于不想进行序列化的变量,使用transient关键字修饰。
+对于不想进行序列化的变量,使用 transient 关键字修饰。
-transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
+transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
-## 34 获取用键盘输入常用的的两种方法
+## 34 获取用键盘输入常用的两种方法
-方法1:通过 Scanner
+方法 1:通过 Scanner
```java
Scanner input = new Scanner(System.in);
@@ -461,15 +530,73 @@ String s = input.nextLine();
input.close();
```
-方法2:通过 BufferedReader
+方法 2:通过 BufferedReader
```java
-BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
-String s = input.readLine();
+BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
+String s = input.readLine();
```
+## 35 Java 中 IO 流
+
+### Java 中 IO 流分为几种?
+
+- 按照流的流向分,可以分为输入流和输出流;
+- 按照操作单元划分,可以划分为字节流和字符流;
+- 按照流的角色划分为节点流和处理流。
+
+Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
+
+- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
+- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
+
+按操作方式分类结构图:
+
+
+
+按操作对象分类结构图:
+
+
+
+### 既然有了字节流,为什么还要有字符流?
+
+问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
+
+回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
+
+### BIO,NIO,AIO 有什么区别?
+
+- **BIO (Blocking I/O):** 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
+- **NIO (Non-blocking/New I/O):** NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
+- **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
+
+## 36. 常见关键字总结:static,final,this,super
+
+详见笔主的这篇文章: https://snailclimb.gitee.io/javaguide/#/docs/java/basic/final,static,this,super
+
+## 37. Collections 工具类和 Arrays 工具类常见方法总结
+
+详见笔主的这篇文章: https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/basic/Arrays,CollectionsCommonMethods.md
+
+## 38. 深拷贝 vs 浅拷贝
+
+1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
+2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
+
+
+
## 参考
- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
- https://www.educba.com/oracle-vs-openjdk/
- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java 面试突击"** 即可免费领取!
+
+**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/Java疑难点.md b/docs/java/Java疑难点.md
new file mode 100644
index 00000000..1a10e958
--- /dev/null
+++ b/docs/java/Java疑难点.md
@@ -0,0 +1,373 @@
+
+
+- [1. 基础](#1-基础)
+ - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
+ - [1.2. 整型包装类值的比较](#12-整型包装类值的比较)
+ - [1.3. BigDecimal](#13-bigdecimal)
+ - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
+ - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
+ - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数)
+ - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项)
+ - [1.3.5. 总结](#135-总结)
+ - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准)
+- [2. 集合](#2-集合)
+ - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南)
+ - [2.1.1. 简介](#211-简介)
+ - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述)
+ - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结)
+ - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist)
+ - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组)
+ - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作)
+
+
+
+# 1. 基础
+
+## 1.1. 正确使用 equals 方法
+
+Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
+
+举个例子:
+
+```java
+// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
+String str = null;
+if (str.equals("SnailClimb")) {
+ ...
+} else {
+ ..
+}
+```
+
+运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。:
+
+```java
+"SnailClimb".equals(str);// false
+```
+不过更推荐使用 `java.util.Objects#equals`(JDK7 引入的工具类)。
+
+```java
+Objects.equals(null,"SnailClimb");// false
+```
+我们看一下`java.util.Objects#equals`的源码就知道原因了。
+```java
+public static boolean equals(Object a, Object b) {
+ // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
+ return (a == b) || (a != null && a.equals(b));
+ }
+```
+
+**注意:**
+
+Reference:[Java中equals方法造成空指针异常的原因及解决方案](https://blog.csdn.net/tick_tock97/article/details/72824894)
+
+- 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
+- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。
+- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
+
+## 1.2. 整型包装类值的比较
+
+所有整型包装类对象值的比较必须使用equals方法。
+
+先看下面这个例子:
+
+```java
+Integer x = 3;
+Integer y = 3;
+System.out.println(x == y);// true
+Integer a = new Integer(3);
+Integer b = new Integer(3);
+System.out.println(a == b);//false
+System.out.println(a.equals(b));//true
+```
+
+当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。
+
+**注意:**如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。
+
+## 1.3. BigDecimal
+
+### 1.3.1. BigDecimal 的用处
+
+《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
+
+```java
+float a = 1.0f - 0.9f;
+float b = 0.9f - 0.8f;
+System.out.println(a);// 0.100000024
+System.out.println(b);// 0.099999964
+System.out.println(a == b);// false
+```
+具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+BigDecimal c = new BigDecimal("0.8");
+BigDecimal x = a.subtract(b);// 0.1
+BigDecimal y = b.subtract(c);// 0.1
+System.out.println(x.equals(y));// true
+```
+
+### 1.3.2. BigDecimal 的大小比较
+
+`a.compareTo(b)` : 返回 -1 表示小于,0 表示 等于, 1表示 大于。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+System.out.println(a.compareTo(b));// 1
+```
+### 1.3.3. BigDecimal 保留几位小数
+
+通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
+
+```java
+BigDecimal m = new BigDecimal("1.255433");
+BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
+System.out.println(n);// 1.255
+```
+
+### 1.3.4. BigDecimal 的使用注意事项
+
+注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
+
+
+
+### 1.3.5. 总结
+
+BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
+
+BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
+
+## 1.4. 基本数据类型与包装数据类型的使用标准
+
+Reference:《阿里巴巴Java开发手册》
+
+- 【强制】所有的 POJO 类属性必须使用包装数据类型。
+- 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
+- 【推荐】所有的局部变量使用基本数据类型。
+
+比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.
+
+**说明** :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
+
+**正例** : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
+
+**反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
+
+# 2. 集合
+
+## 2.1. Arrays.asList()使用指南
+
+最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。
+
+### 2.1.1. 简介
+
+`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。
+
+```java
+String[] myArray = { "Apple", "Banana", "Orange" };
+List myList = Arrays.asList(myArray);
+//上面两个语句等价于下面一条语句
+List myList = Arrays.asList("Apple","Banana", "Orange");
+```
+
+JDK 源码对于这个方法的说明:
+
+```java
+/**
+ *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
+ */
+public static List asList(T... a) {
+ return new ArrayList<>(a);
+}
+```
+
+### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述
+
+`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述:
+
+方法.png)
+
+### 2.1.3. 使用时的注意事项总结
+
+**传递的数组必须是对象数组,而不是基本类型。**
+
+`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。
+
+```java
+int[] myArray = { 1, 2, 3 };
+List myList = Arrays.asList(myArray);
+System.out.println(myList.size());//1
+System.out.println(myList.get(0));//数组地址值
+System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
+int [] array=(int[]) myList.get(0);
+System.out.println(array[0]);//1
+```
+当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。
+
+我们使用包装类型数组就可以解决这个问题。
+
+```java
+Integer[] myArray = { 1, 2, 3 };
+```
+
+**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。**
+
+```java
+List myList = Arrays.asList(1, 2, 3);
+myList.add(4);//运行时报错:UnsupportedOperationException
+myList.remove(1);//运行时报错:UnsupportedOperationException
+myList.clear();//运行时报错:UnsupportedOperationException
+```
+
+`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
+
+```java
+List myList = Arrays.asList(1, 2, 3);
+System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
+```
+
+下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。
+
+```java
+ private static class ArrayList extends AbstractList
+ implements RandomAccess, java.io.Serializable
+ {
+ ...
+
+ @Override
+ public E get(int index) {
+ ...
+ }
+
+ @Override
+ public E set(int index, E element) {
+ ...
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ ...
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ ...
+ }
+
+ @Override
+ public void forEach(Consumer super E> action) {
+ ...
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator operator) {
+ ...
+ }
+
+ @Override
+ public void sort(Comparator super E> c) {
+ ...
+ }
+ }
+```
+
+我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。
+
+```java
+public E remove(int index) {
+ throw new UnsupportedOperationException();
+}
+```
+
+### 2.1.4. 如何正确的将数组转换为ArrayList?
+
+stackoverflow:https://dwz.cn/vcBkTiTW
+
+**1. 自己动手实现(教育目的)**
+
+```java
+//JDK1.5+
+static List arrayToList(final T[] array) {
+ final List l = new ArrayList(array.length);
+
+ for (final T s : array) {
+ l.add(s);
+ }
+ return (l);
+}
+```
+
+```java
+Integer [] myArray = { 1, 2, 3 };
+System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
+```
+
+**2. 最简便的方法(推荐)**
+
+```java
+List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
+```
+
+**3. 使用 Java8 的Stream(推荐)**
+
+```java
+Integer [] myArray = { 1, 2, 3 };
+List myList = Arrays.stream(myArray).collect(Collectors.toList());
+//基本类型也可以实现转换(依赖boxed的装箱操作)
+int [] myArray2 = { 1, 2, 3 };
+List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
+```
+
+**4. 使用 Guava(推荐)**
+
+对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)
+
+```java
+List il = ImmutableList.of("string", "elements"); // from varargs
+List il = ImmutableList.copyOf(aStringArray); // from array
+```
+对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:
+
+```java
+List l1 = Lists.newArrayList(anotherListOrCollection); // from collection
+List l2 = Lists.newArrayList(aStringArray); // from array
+List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
+```
+
+**5. 使用 Apache Commons Collections**
+
+```java
+List list = new ArrayList();
+CollectionUtils.addAll(list, str);
+```
+
+## 2.2. Collection.toArray()方法使用的坑&如何反转数组
+
+该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。
+
+```java
+String [] s= new String[]{
+ "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
+};
+List list = Arrays.asList(s);
+Collections.reverse(list);
+s=list.toArray(new String[0]);//没有指定类型的话会报错
+```
+
+由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:
+
+## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作
+
+如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**。
+
+> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。
+
+`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。
+
+
+
+
+
diff --git a/docs/java/Java编程规范.md b/docs/java/Java编程规范.md
index b96a6726..6b4731ef 100644
--- a/docs/java/Java编程规范.md
+++ b/docs/java/Java编程规范.md
@@ -1,6 +1,30 @@
+讲真的,下面推荐的文章或者资源建议阅读 3 遍以上。
+### 团队
-根据各位建议加上了这部分内容,我暂时只是给出了两个资源,后续可能会对重要的点进行总结,然后更新在这里,如果你总结过这类东西,欢迎与我联系!
+- **阿里巴巴Java开发手册(详尽版)**
+- **Google Java编程风格指南:**
-- **阿里巴巴Java开发手册(详尽版)**
-- **Google Java编程风格指南:**
\ No newline at end of file
+### 个人
+
+- **程序员你为什么这么累:**
+
+### 如何写出优雅的 Java 代码
+
+1. 使用 IntelliJ IDEA 作为您的集成开发环境 (IDE)
+1. 使用 JDK 8 或更高版本
+1. 使用 Maven/Gradle
+1. 使用 Lombok
+1. 编写单元测试
+1. 重构:常见,但也很慢
+1. 注意代码规范
+1. 定期联络客户,以获取他们的反馈
+
+上述建议的详细内容:[八点建议助您写出优雅的Java代码](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485140&idx=1&sn=ecaeace613474f1859aaeed0282ae680&chksm=cea2491ff9d5c00982ffaece847ce1aead89fdb3fe190752d9837c075c79fc95db5940992c56&token=1328169465&lang=zh_CN&scene=21#wechat_redirect)。
+
+更多代码优化相关内容推荐:
+
+- [业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!](https://juejin.im/post/5dad23685188251d2c4ea2b6)
+- [一些不错的 Java 实践!推荐阅读3遍以上!](http://lrwinx.github.io/2017/03/04/%E7%BB%86%E6%80%9D%E6%9E%81%E6%81%90-%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BC%9A%E5%86%99java%E5%90%97/)
+- [[解锁新姿势] 兄dei,你代码需要优化了](https://juejin.im/post/5dafbc02e51d4524a0060bdd)
+- [消灭 Java 代码的“坏味道”](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485599&idx=1&sn=d83ff4e6b1ee951a0a33508a10980ea3&chksm=cea24754f9d5ce426d18b435a8c373ddc580c06c7d6a45cc51377361729c31c7301f1bbc3b78&token=1328169465&lang=zh_CN#rd)
\ No newline at end of file
diff --git a/docs/java/Java虚拟机(jvm).md b/docs/java/Java虚拟机(jvm).md
deleted file mode 100644
index 9be88bc8..00000000
--- a/docs/java/Java虚拟机(jvm).md
+++ /dev/null
@@ -1,59 +0,0 @@
-
-下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。
-
-
-> ### 常见面试题
-
-[深入理解虚拟机之Java内存区域](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484960&idx=1&sn=ff3739fe849030178346bef28a4556c3&chksm=cea249ebf9d5c0fdbde7c86155d0d7ac8925153742aff472bcb79e5e9d400534a855bad38375&token=1082669959&lang=zh_CN#rd)
-
-1. 介绍下Java内存区域(运行时数据区)。
-
-2. 对象的访问定位的两种方式。
-
-
-[深入理解虚拟机之垃圾回收](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484959&idx=1&sn=9ac740edba59981b7c89482043776280&chksm=cea249d4f9d5c0c21703382510a47d4bb387932bd814ac891fd214b92cead5d2cf0ee2dff797&token=1082669959&lang=zh_CN#rd)
-
-1. 如何判断对象是否死亡(两种方法)。
-
-2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
-
-3. 垃圾收集有哪些算法,各自的特点?
-
-4. HotSpot为什么要分为新生代和老年代?
-
-5. 常见的垃圾回收器有那些?
-
-6. 介绍一下CMS,G1收集器。
-
-7. Minor Gc和Full GC 有什么不同呢?
-
-
-
- [虚拟机性能监控和故障处理工具](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484957&idx=1&sn=713ed6003d23ef883ded14cb43e9ebb7&chksm=cea249d6f9d5c0c0ce0854a03f0d02fcacc8a46e29c2fd4f085a375b00e1cd1b632937a9895e&token=1082669959&lang=zh_CN#rd)
-
-1. JVM调优的常见命令行工具有哪些?
-
- [深入理解虚拟机之类文件结构](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484956&idx=1&sn=05f46ccacacdbce7c43de594d3fe93db&chksm=cea249d7f9d5c0c1ef6d29b0fbbf0701acd28490deb0974ae71b4d23ae793bec0b0993a4c829&token=1082669959&lang=zh_CN#rd)
-
-1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?)
-
-[深入理解虚拟机之虚拟机字节码执行引擎](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484952&idx=1&sn=d0ec9443600dc5b2a81782b7ae0691d5&chksm=cea249d3f9d5c0c50642f1829fd6fe9e35d155bbbb6718611330c7c46c7158279275b533181e&token=1082669959&lang=zh_CN#rd)
-
-1. 简单说说类加载过程,里面执行了哪些操作?
-
-2. 对类加载器有了解吗?
-
-3. 什么是双亲委派模型?
-
-4. 双亲委派模型的工作过程以及使用它的好处。
-
-
-
-
-
-> ### 推荐阅读
-
- [《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章)
- [全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461)
-
-
diff --git a/docs/java/Java集合框架常见面试题总结.md b/docs/java/Java集合框架常见面试题总结.md
deleted file mode 100644
index cb0bd1fe..00000000
--- a/docs/java/Java集合框架常见面试题总结.md
+++ /dev/null
@@ -1,353 +0,0 @@
-
-
-1. [List,Set,Map三者的区别及总结](#list,setmap三者的区别及总结)
-1. [Arraylist 与 LinkedList 区别](#arraylist-与-linkedlist-区别)
-1. [ArrayList 与 Vector 区别(为什么要用Arraylist取代Vector呢?)](#arraylist-与-vector-区别)
-1. [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
-1. [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)
-1. [HashMap 和 ConcurrentHashMap 的区别](#hashmap-和-concurrenthashmap-的区别)
-1. [HashSet如何检查重复](#hashset如何检查重复)
-1. [comparable 和 comparator的区别](#comparable-和-comparator的区别)
- 1. [Comparator定制排序](#comparator定制排序)
- 1. [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)
-1. [如何对Object的list排序?](#如何对object的list排序)
-1. [如何实现数组与List的相互转换?](#如何实现数组与list的相互转换)
-1. [如何求ArrayList集合的交集 并集 差集 去重复并集](#如何求arraylist集合的交集-并集-差集-去重复并集)
-1. [HashMap 的工作原理及代码实现](#hashmap-的工作原理及代码实现)
-1. [ConcurrentHashMap 的工作原理及代码实现](#concurrenthashmap-的工作原理及代码实现)
-1. [集合框架底层数据结构总结](#集合框架底层数据结构总结)
- 1. [- Collection](#--collection)
- 1. [1. List](#1-list)
- 1. [2. Set](#2-set)
- 1. [- Map](#--map)
-1. [集合的选用](#集合的选用)
-1. [集合的常用方法](#集合的常用方法)
-
-
-
-
-## List,Set,Map三者的区别及总结
-- **List:对付顺序的好帮手**
-
- List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
-- **Set:注重独一无二的性质**
-
- 不允许重复的集合。不会有多个元素引用相同的对象。
-
-- **Map:用Key来搜索的专家**
-
- 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
-
-
-## Arraylist 与 LinkedList 区别
-Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向链表数据结构(插入,删除效率特别高)(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。
-
-## ArrayList 与 Vector 区别
-Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector
-,代码要在同步操作上耗费大量的时间。Arraylist不是同步的,所以在不需要同步时建议使用Arraylist。
-
-## HashMap 和 Hashtable 的区别
-1. HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。
-
-2. 因为线程安全的问题,HashMap要比HashTable效率高一点,HashTable基本被淘汰。
-3. HashMap允许有null值的存在,而在HashTable中put进的键值只要有一个null,直接抛出NullPointerException。
-
-Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在你需要完全的线程安全的时候使用Hashtable,而如果你使用Java5或以上的话,请使用ConcurrentHashMap吧
-
-## HashSet 和 HashMap 区别
-
-
-## HashMap 和 ConcurrentHashMap 的区别
-[HashMap与ConcurrentHashMap的区别](https://blog.csdn.net/xuefeng0707/article/details/40834595)
-
-1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
-2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
-
-## HashSet如何检查重复
-当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版)
-
-**hashCode()与equals()的相关规定:**
-1. 如果两个对象相等,则hashcode一定也是相同的
-2. 两个对象相等,对两个equals方法返回true
-3. 两个对象有相同的hashcode值,它们也不一定是相等的
-4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
-
-**==与equals的区别**
-
-1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
-2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
-3. ==指引用是否相同 equals()指的是值是否相同
-
-## comparable 和 comparator的区别
-- comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
-- comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
-
-一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().
-
-### Comparator定制排序
-```java
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-/**
- * TODO Collections类方法测试之排序
- * @author 寇爽
- * @date 2017年11月20日
- * @version 1.8
- */
-public class CollectionsSort {
-
- public static void main(String[] args) {
-
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
- // void reverse(List list):反转
- Collections.reverse(arrayList);
- System.out.println("Collections.reverse(arrayList):");
- System.out.println(arrayList);
-/*
- * void rotate(List list, int distance),旋转。
- * 当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将
- * list的前distance个元素整体移到后面。
-
- Collections.rotate(arrayList, 4);
- System.out.println("Collections.rotate(arrayList, 4):");
- System.out.println(arrayList);*/
-
- // void sort(List list),按自然排序的升序排序
- Collections.sort(arrayList);
- System.out.println("Collections.sort(arrayList):");
- System.out.println(arrayList);
-
- // void shuffle(List list),随机排序
- Collections.shuffle(arrayList);
- System.out.println("Collections.shuffle(arrayList):");
- System.out.println(arrayList);
-
- // 定制排序的用法
- Collections.sort(arrayList, new Comparator() {
-
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2.compareTo(o1);
- }
- });
- System.out.println("定制排序后:");
- System.out.println(arrayList);
- }
-
-}
-
-```
-### 重写compareTo方法实现按年龄来排序
-```java
-package map;
-
-import java.util.Set;
-import java.util.TreeMap;
-
-public class TreeMap2 {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- TreeMap pdata = new TreeMap();
- pdata.put(new Person("张三", 30), "zhangsan");
- pdata.put(new Person("李四", 20), "lisi");
- pdata.put(new Person("王五", 10), "wangwu");
- pdata.put(new Person("小红", 5), "xiaohong");
- // 得到key的值的同时得到key所对应的值
- Set keys = pdata.keySet();
- for (Person key : keys) {
- System.out.println(key.getAge() + "-" + key.getName());
-
- }
- }
-}
-
-// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
-// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
-// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
-
-class Person implements Comparable {
- private String name;
- private int age;
-
- public Person(String name, int age) {
- super();
- this.name = name;
- this.age = age;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- /**
- * TODO重写compareTo方法实现按年龄来排序
- */
- @Override
- public int compareTo(Person o) {
- // TODO Auto-generated method stub
- if (this.age > o.getAge()) {
- return 1;
- } else if (this.age < o.getAge()) {
- return -1;
- }
- return age;
- }
-}
-```
-
-## 如何对Object的list排序
-- 对objects数组进行排序,我们可以用Arrays.sort()方法
-- 对objects的集合进行排序,需要使用Collections.sort()方法
-
-
-## 如何实现数组与List的相互转换
-List转数组:toArray(arraylist.size()方法;数组转List:Arrays的asList(a)方法
-```java
-List arrayList = new ArrayList();
- arrayList.add("s");
- arrayList.add("e");
- arrayList.add("n");
- /**
- * ArrayList转数组
- */
- int size=arrayList.size();
- String[] a = arrayList.toArray(new String[size]);
- //输出第二个元素
- System.out.println(a[1]);//结果:e
- //输出整个数组
- System.out.println(Arrays.toString(a));//结果:[s, e, n]
- /**
- * 数组转list
- */
- List list=Arrays.asList(a);
- /**
- * list转Arraylist
- */
- List arrayList2 = new ArrayList();
- arrayList2.addAll(list);
- System.out.println(list);
-```
-## 如何求ArrayList集合的交集 并集 差集 去重复并集
-需要用到List接口中定义的几个方法:
-
-- addAll(Collection extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
-实例代码:
-- retainAll(Collection> c): 仅保留此列表中包含在指定集合中的元素。
-- removeAll(Collection> c) :从此列表中删除指定集合中包含的所有元素。
-```java
-package list;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- *TODO 两个集合之间求交集 并集 差集 去重复并集
- * @author 寇爽
- * @date 2017年11月21日
- * @version 1.8
- */
-public class MethodDemo {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- List list1 = new ArrayList();
- list1.add(1);
- list1.add(2);
- list1.add(3);
- list1.add(4);
-
- List list2 = new ArrayList();
- list2.add(2);
- list2.add(3);
- list2.add(4);
- list2.add(5);
- // 并集
- // list1.addAll(list2);
- // 交集
- //list1.retainAll(list2);
- // 差集
- // list1.removeAll(list2);
- // 无重复并集
- list2.removeAll(list1);
- list1.addAll(list2);
- for (Integer i : list1) {
- System.out.println(i);
- }
- }
-
-}
-
-```
-
-## HashMap 的工作原理及代码实现
-
-[集合框架源码学习之HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)
-
-## ConcurrentHashMap 的工作原理及代码实现
-
-[ConcurrentHashMap实现原理及源码分析](http://www.cnblogs.com/chengxiao/p/6842045.html)
-
-
-## 集合框架底层数据结构总结
-### - Collection
-
-#### 1. List
- - Arraylist:数组(查询快,增删慢 线程不安全,效率高 )
- - Vector:数组(查询快,增删慢 线程安全,效率低 )
- - LinkedList:链表(查询慢,增删快 线程不安全,效率高 )
-
-#### 2. Set
- - HashSet(无序,唯一):哈希表或者叫散列集(hash table)
- - LinkedHashSet:链表和哈希表组成 。 由链表保证元素的排序 , 由哈希表证元素的唯一性
- - TreeSet(有序,唯一):红黑树(自平衡的排序二叉树。)
-
-### - Map
- - HashMap:基于哈希表的Map接口实现(哈希表对键进行散列,Map结构即映射表存放键值对)
- - LinkedHashMap:HashMap 的基础上加上了链表数据结构
- - HashTable:哈希表
- - TreeMap:红黑树(自平衡的排序二叉树)
-
-
-## 集合的选用
-主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。
-
-2018/3/11更新
-## 集合的常用方法
-今天下午无意看见一道某大厂的面试题,面试题的内容就是问你某一个集合常见的方法有哪些。虽然平时也经常见到这些集合,但是猛一下让我想某一个集合的常用的方法难免会有遗漏或者与其他集合搞混,所以建议大家还是照着API文档把常见的那几个集合的常用方法看一看。
-
-会持续更新。。。
-
-**参考书籍:**
-
-《Head first java 》第二版 推荐阅读真心不错 (适合基础较差的)
-
- 《Java核心技术卷1》推荐阅读真心不错 (适合基础较好的)
-
- 《算法》第四版 (适合想对数据结构的Java实现感兴趣的)
-
diff --git a/docs/java/Multithread/AQS.md b/docs/java/Multithread/AQS.md
index f405db1f..facba05d 100644
--- a/docs/java/Multithread/AQS.md
+++ b/docs/java/Multithread/AQS.md
@@ -1,73 +1,70 @@
+点击关注[公众号](#公众号 "公众号")及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。
-**目录:**
-
+
- [1 AQS 简单介绍](#1-aqs-简单介绍)
- [2 AQS 原理](#2-aqs-原理)
- - [2.1 AQS 原理概览](#21-aqs-原理概览)
- - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式)
- - [2.3 AQS底层使用了模板方法模式](#23-aqs底层使用了模板方法模式)
-- [3 Semaphore\(信号量\)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问)
+ - [2.1 AQS 原理概览](#21-aqs-原理概览)
+ - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式)
+ - [2.3 AQS 底层使用了模板方法模式](#23-aqs-底层使用了模板方法模式)
+- [3 Semaphore(信号量)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问)
- [4 CountDownLatch (倒计时器)](#4-countdownlatch-倒计时器)
- - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法)
- - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例)
- - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足)
- - [4.4 CountDownLatch相常见面试题:](#44-countdownlatch相常见面试题)
-- [5 CyclicBarrier\(循环栅栏\)](#5-cyclicbarrier循环栅栏)
- - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景)
- - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例)
- - [5.3 CyclicBarrier和CountDownLatch的区别](#53-cyclicbarrier和countdownlatch的区别)
+ - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法)
+ - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例)
+ - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足)
+ - [4.4 CountDownLatch 常见面试题](#44-countdownlatch-相常见面试题)
+- [5 CyclicBarrier(循环栅栏)](#5-cyclicbarrier循环栅栏)
+ - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景)
+ - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例)
+ - [5.3 `CyclicBarrier`源码分析](#53-cyclicbarrier源码分析)
+ - [5.4 CyclicBarrier 和 CountDownLatch 的区别](#54-cyclicbarrier-和-countdownlatch-的区别)
- [6 ReentrantLock 和 ReentrantReadWriteLock](#6-reentrantlock-和-reentrantreadwritelock)
+- [参考](#参考)
+- [公众号](#公众号)
-
-
-> 常见问题:AQS原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗?
-
-**本节思维导图:**
-
-
+
+> 常见问题:AQS 原理?;CountDownLatch 和 CyclicBarrier 了解吗,两者的区别是什么?用过 Semaphore 吗?
### 1 AQS 简单介绍
-AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
+
+AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面。

-AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
+AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于 AQS 的。当然,我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。
### 2 AQS 原理
-> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
+> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于 AQS 原理的理解”。下面给大家一个示例供大家参考,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
-下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
+下面大部分内容其实在 AQS 类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
#### 2.1 AQS 原理概览
-**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
+**AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
-> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
-
-看个AQS(AbstractQueuedSynchronizer)原理图:
+> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。
+看个 AQS(AbstractQueuedSynchronizer)原理图:

-AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
+AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。
```java
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
```
-状态信息通过protected类型的getState,setState,compareAndSetState进行操作
+状态信息通过 protected 类型的`getState`,`setState`,`compareAndSetState`进行操作
```java
-
//返回同步状态的当前值
-protected final int getState() {
+protected final int getState() {
return state;
}
// 设置同步状态的值
-protected final void setState(int newState) {
+protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
@@ -78,29 +75,144 @@ protected final boolean compareAndSetState(int expect, int update) {
#### 2.2 AQS 对资源的共享方式
-**AQS定义两种资源共享方式**
+**AQS 定义两种资源共享方式**
-- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
-- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
+**1)Exclusive**(独占)
-ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
+只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁,下面以 ReentrantLock 对这两种锁的定义做介绍:
-不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。
+- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
+- 非公平锁:当线程要获取锁时,先通过两次 CAS 操作去抢锁,如果没抢到,当前线程再加入到队列中等待唤醒。
-#### 2.3 AQS底层使用了模板方法模式
+> 说明:下面这部分关于 `ReentrantLock` 源代码内容节选自:https://www.javadoop.com/post/AbstractQueuedSynchronizer-2 ,这是一篇很不错文章,推荐阅读。
+
+**下面来看 ReentrantLock 中相关的源代码:**
+
+ReentrantLock 默认采用非公平锁,因为考虑获得更好的性能,通过 boolean 来决定是否用公平锁(传入 true 用公平锁)。
+
+```java
+/** Synchronizer providing all implementation mechanics */
+private final Sync sync;
+public ReentrantLock() {
+ // 默认非公平锁
+ sync = new NonfairSync();
+}
+public ReentrantLock(boolean fair) {
+ sync = fair ? new FairSync() : new NonfairSync();
+}
+```
+
+ReentrantLock 中公平锁的 `lock` 方法
+
+```java
+static final class FairSync extends Sync {
+ final void lock() {
+ acquire(1);
+ }
+ // AbstractQueuedSynchronizer.acquire(int arg)
+ public final void acquire(int arg) {
+ if (!tryAcquire(arg) &&
+ acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
+ selfInterrupt();
+ }
+ protected final boolean tryAcquire(int acquires) {
+ final Thread current = Thread.currentThread();
+ int c = getState();
+ if (c == 0) {
+ // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
+ if (!hasQueuedPredecessors() &&
+ compareAndSetState(0, acquires)) {
+ setExclusiveOwnerThread(current);
+ return true;
+ }
+ }
+ else if (current == getExclusiveOwnerThread()) {
+ int nextc = c + acquires;
+ if (nextc < 0)
+ throw new Error("Maximum lock count exceeded");
+ setState(nextc);
+ return true;
+ }
+ return false;
+ }
+}
+```
+
+非公平锁的 lock 方法:
+
+```java
+static final class NonfairSync extends Sync {
+ final void lock() {
+ // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
+ if (compareAndSetState(0, 1))
+ setExclusiveOwnerThread(Thread.currentThread());
+ else
+ acquire(1);
+ }
+ // AbstractQueuedSynchronizer.acquire(int arg)
+ public final void acquire(int arg) {
+ if (!tryAcquire(arg) &&
+ acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
+ selfInterrupt();
+ }
+ protected final boolean tryAcquire(int acquires) {
+ return nonfairTryAcquire(acquires);
+ }
+}
+/**
+ * Performs non-fair tryLock. tryAcquire is implemented in
+ * subclasses, but both need nonfair try for trylock method.
+ */
+final boolean nonfairTryAcquire(int acquires) {
+ final Thread current = Thread.currentThread();
+ int c = getState();
+ if (c == 0) {
+ // 这里没有对阻塞队列进行判断
+ if (compareAndSetState(0, acquires)) {
+ setExclusiveOwnerThread(current);
+ return true;
+ }
+ }
+ else if (current == getExclusiveOwnerThread()) {
+ int nextc = c + acquires;
+ if (nextc < 0) // overflow
+ throw new Error("Maximum lock count exceeded");
+ setState(nextc);
+ return true;
+ }
+ return false;
+}
+```
+
+总结:公平锁和非公平锁只有两处不同:
+
+1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
+2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。
+
+公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
+
+相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
+
+**2)Share**(共享)
+
+多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
+
+ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 也就是读写锁允许多个线程同时对某一资源进行读。
+
+不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在上层已经帮我们实现好了。
+
+#### 2.3 AQS 底层使用了模板方法模式
同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
-1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
-2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
+1. 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)
+2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。
> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。
-**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:**
+**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:**
```java
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
@@ -111,28 +223,28 @@ tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true
```
-默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
+默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS 类中的其他方法都是 final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
-以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
+以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程 lock()时,会调用 tryAcquire()独占该锁并将 state+1。此后,其他线程再 tryAcquire()时就会失败,直到 A 线程 unlock()到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的。
-再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
+再以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后 countDown()一次,state 会 CAS(Compare and Swap)减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从 await()函数返回,继续后余动作。
-一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
+一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
推荐两篇 AQS 原理和相关源码分析的文章:
- http://www.cnblogs.com/waterystone/p/4920797.html
- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
-
-
### 3 Semaphore(信号量)-允许多个线程同时访问
-**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下:
+**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。**
+
+示例代码如下:
```java
/**
- *
+ *
* @author Snailclimb
* @date 2018年9月30日
* @Description: 需要一次性拿一个许可的情况
@@ -173,22 +285,21 @@ public class SemaphoreExample1 {
}
```
-执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。
+执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的 acquire 方法。然而,其实并没有实际的许可证这个对象,Semaphore 只是维持了一个可获得许可证的数量。 Semaphore 经常用于限制获取某种资源的线程数量。
当然一次也可以一次拿取和释放多个许可,不过一般没有必要这样做:
```java
- semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4
- test(threadnum);
- semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4
+semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4
+test(threadnum);
+semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4
```
-除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回false。
-
+除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回 false。
Semaphore 有两种模式,公平模式和非公平模式。
-- **公平模式:** 调用acquire的顺序就是获取许可证的顺序,遵循FIFO;
+- **公平模式:** 调用 acquire 的顺序就是获取许可证的顺序,遵循 FIFO;
- **非公平模式:** 抢占式的。
**Semaphore 对应的两个构造方法如下:**
@@ -202,29 +313,30 @@ Semaphore 有两种模式,公平模式和非公平模式。
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
```
-**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。**
-由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章:
+**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。**
-- https://blog.csdn.net/qq_19431333/article/details/70212663
+[issue645补充内容](https://github.com/Snailclimb/JavaGuide/issues/645) :Semaphore与CountDownLatch一样,也是共享锁的一种实现。它默认构造AQS的state为permits。当执行任务的线程数量超出permits,那么多余的线程将会被放入阻塞队列Park,并自旋判断state是否大于0。只有当state大于0的时候,阻塞的线程才能继续执行,此时先前执行任务的线程继续执行release方法,release方法使得state的变量会加1,那么自旋的线程便会判断成功。
+如此,每次只有最多不超过permits数量的线程能自旋成功,便限制了执行任务线程的数量。
+
+由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下这篇文章:https://juejin.im/post/5ae755366fb9a07ab508adc6
### 4 CountDownLatch (倒计时器)
-CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。
+CountDownLatch允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕。在 Java 并发中,countdownlatch 的概念是一个常见的面试题,所以一定要确保你很好的理解了它。
-#### 4.1 CountDownLatch 的三种典型用法
+CountDownLatch是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用countDown方法时,其实使用了`tryReleaseShared`方法以CAS的操作来减少state,直至state为0就代表所有的线程都调用了countDown方法。当调用await方法的时候,如果state不为0,就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入阻塞队列Park,并自旋CAS判断state == 0,直至最后一个线程调用了countDown,使得state == 0,于是阻塞的线程便判断成功,全部往下执行。
-①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :`new CountDownLatch(n) `,每当一个任务线程执行完毕,就将计数器减1 `countdownlatch.countDown()`,当计数器的值变为0时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
+#### 4.1 CountDownLatch 的两种典型用法
-②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1) `,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
-
-③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
+1. 某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :`new CountDownLatch(n)`,每当一个任务线程执行完毕,就将计数器减 1 `countdownlatch.countDown()`,当计数器的值变为 0 时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
+2. 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1)`,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。
#### 4.2 CountDownLatch 的使用示例
```java
/**
- *
+ *
* @author SnailClimb
* @date 2018年10月1日
* @Description: CountDownLatch 使用方法示例
@@ -264,23 +376,36 @@ public class CountDownLatchExample1 {
}
```
-上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行`System.out.println("finish");`。
-与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
+上面的代码中,我们定义了请求的数量为 550,当这 550 个请求被处理完成之后,才会执行`System.out.println("finish");`。
-其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
+与 CountDownLatch 的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用 `CountDownLatch.await()` 方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
+
+其他 N 个线程必须引用闭锁对象,因为他们需要通知 `CountDownLatch` 对象,他们已经完成了各自的任务。这种通知机制是通过 `CountDownLatch.countDown()`方法来完成的;每调用一次这个方法,在构造函数中初始化的 count 值就减 1。所以当 N 个线程都调 用了这个方法,count 的值等于 0,然后主线程就能通过 `await()`方法,恢复执行自己的任务。
+
+再插一嘴:`CountDownLatch` 的 `await()` 方法使用不当很容易产生死锁,比如我们上面代码中的 for 循环改为:
+
+```java
+for (int i = 0; i < threadCount-1; i++) {
+.......
+}
+```
+
+这样就导致 `count` 的值没办法等于 0,然后就会导致一直等待。
+
+如果对CountDownLatch源码感兴趣的朋友,可以查看: [【JUC】JDK1.8源码分析之CountDownLatch(五)](https://www.cnblogs.com/leesf456/p/5406191.html)
#### 4.3 CountDownLatch 的不足
-CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
+CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。
-#### 4.4 CountDownLatch相常见面试题:
+#### 4.4 CountDownLatch 相常见面试题
-解释一下CountDownLatch概念?
+解释一下 CountDownLatch 概念?
-CountDownLatch 和CyclicBarrier的不同之处?
+CountDownLatch 和 CyclicBarrier 的不同之处?
-给出一些CountDownLatch使用的例子?
+给出一些 CountDownLatch 使用的例子?
CountDownLatch 类中主要的方法?
@@ -288,19 +413,38 @@ CountDownLatch 类中主要的方法?
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
-CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
+> CountDownLatch的实现是基于AQS的,而CycliBarrier是基于 ReentrantLock(ReentrantLock也属于AQS同步器)和 Condition 的.
+
+CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
+
+再来看一下它的构造函数:
+
+```java
+public CyclicBarrier(int parties) {
+ this(parties, null);
+}
+
+public CyclicBarrier(int parties, Runnable barrierAction) {
+ if (parties <= 0) throw new IllegalArgumentException();
+ this.parties = parties;
+ this.count = parties;
+ this.barrierCommand = barrierAction;
+}
+```
+
+其中,parties 就代表了有拦截的线程的数量,当拦截的线程数量达到这个值的时候就打开栅栏,让所有线程通过。
#### 5.1 CyclicBarrier 的应用场景
-CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
+CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个 Excel 保存了用户所有银行流水,每个 Sheet 保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个 sheet 里的银行流水,都执行完之后,得到每个 sheet 的日均银行流水,最后,再用 barrierAction 用这些线程的计算结果,计算出整个 Excel 的日均银行流水。
#### 5.2 CyclicBarrier 的使用示例
-示例1:
+示例 1:
```java
/**
- *
+ *
* @author Snailclimb
* @date 2018年10月1日
* @Description: 测试 CyclicBarrier 类中带参数的 await() 方法
@@ -336,7 +480,7 @@ public class CyclicBarrierExample2 {
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready");
try {
- /**等待60秒,保证子线程完全执行结束*/
+ /**等待60秒,保证子线程完全执行结束*/
cyclicBarrier.await(60, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("-----CyclicBarrierException------");
@@ -372,13 +516,14 @@ threadnum:7is finish
threadnum:6is finish
......
```
-可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。
-另外,CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下:
+可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。
+
+另外,CyclicBarrier 还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下:
```java
/**
- *
+ *
* @author SnailClimb
* @date 2018年10月1日
* @Description: 新建 CyclicBarrier 的时候指定一个 Runnable
@@ -449,28 +594,136 @@ threadnum:8is finish
threadnum:7is finish
......
```
-#### 5.3 CyclicBarrier和CountDownLatch的区别
-CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的:
+#### 5.3 `CyclicBarrier`源码分析
+
+当调用 `CyclicBarrier` 对象调用 `await()` 方法时,实际上调用的是`dowait(false, 0L)`方法。 `await()` 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。
+
+```java
+ public int await() throws InterruptedException, BrokenBarrierException {
+ try {
+ return dowait(false, 0L);
+ } catch (TimeoutException toe) {
+ throw new Error(toe); // cannot happen
+ }
+ }
+```
+
+`dowait(false, 0L)`:
+
+```java
+ // 当线程数量或者请求数量达到 count 时 await 之后的方法才会被执行。上面的示例中 count 的值就为 5。
+ private int count;
+ /**
+ * Main barrier code, covering the various policies.
+ */
+ private int dowait(boolean timed, long nanos)
+ throws InterruptedException, BrokenBarrierException,
+ TimeoutException {
+ final ReentrantLock lock = this.lock;
+ // 锁住
+ lock.lock();
+ try {
+ final Generation g = generation;
+
+ if (g.broken)
+ throw new BrokenBarrierException();
+
+ // 如果线程中断了,抛出异常
+ if (Thread.interrupted()) {
+ breakBarrier();
+ throw new InterruptedException();
+ }
+ // cout减1
+ int index = --count;
+ // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行await 方法之后的条件
+ if (index == 0) { // tripped
+ boolean ranAction = false;
+ try {
+ final Runnable command = barrierCommand;
+ if (command != null)
+ command.run();
+ ranAction = true;
+ // 将 count 重置为 parties 属性的初始化值
+ // 唤醒之前等待的线程
+ // 下一波执行开始
+ nextGeneration();
+ return 0;
+ } finally {
+ if (!ranAction)
+ breakBarrier();
+ }
+ }
+
+ // loop until tripped, broken, interrupted, or timed out
+ for (;;) {
+ try {
+ if (!timed)
+ trip.await();
+ else if (nanos > 0L)
+ nanos = trip.awaitNanos(nanos);
+ } catch (InterruptedException ie) {
+ if (g == generation && ! g.broken) {
+ breakBarrier();
+ throw ie;
+ } else {
+ // We're about to finish waiting even if we had not
+ // been interrupted, so this interrupt is deemed to
+ // "belong" to subsequent execution.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ if (g.broken)
+ throw new BrokenBarrierException();
+
+ if (g != generation)
+ return index;
+
+ if (timed && nanos <= 0L) {
+ breakBarrier();
+ throw new TimeoutException();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+```
+
+总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,cout 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。
+
+#### 5.4 CyclicBarrier 和 CountDownLatch 的区别
+
+**下面这个是国外一个大佬的回答:**
+
+CountDownLatch 是计数器,只能使用一次,而 CyclicBarrier 的计数器提供 reset 功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从 jdk 作者设计的目的来看,javadoc 是这么描述它们的:
> CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;)
> CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。)
-对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
+对于 CountDownLatch 来说,重点是“一个线程(多个线程)等待”,而其他的 N 个线程在完成“某件事情”之后,可以终止,也可以等待。而对于 CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
-CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
-
-
-
-CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章:
-
-- https://blog.csdn.net/u010185262/article/details/54692886
-- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0
+CountDownLatch 是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而 CyclicBarrier 更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
### 6 ReentrantLock 和 ReentrantReadWriteLock
ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。
-由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。
+### 参考
+
+- https://juejin.im/post/5ae755256fb9a07ac3634067
+- https://blog.csdn.net/u010185262/article/details/54692886
+- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0
+
+### 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号 "公众号")后台回复 **"面试突击"** 即可免费领取!
+
+**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
-- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd)
diff --git a/docs/java/Multithread/Atomic.md b/docs/java/Multithread/Atomic.md
index 33dd7ef3..a63d16d9 100644
--- a/docs/java/Multithread/Atomic.md
+++ b/docs/java/Multithread/Atomic.md
@@ -1,8 +1,28 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
> 个人觉得这一节掌握基本的使用即可!
-**本节思维导图:**
+
-
+- [1 Atomic 原子类介绍](#1-atomic-原子类介绍)
+- [2 基本类型原子类](#2-基本类型原子类)
+ - [2.1 基本类型原子类介绍](#21-基本类型原子类介绍)
+ - [2.2 AtomicInteger 常见方法使用](#22-atomicinteger-常见方法使用)
+ - [2.3 基本数据类型原子类的优势](#23-基本数据类型原子类的优势)
+ - [2.4 AtomicInteger 线程安全原理简单分析](#24-atomicinteger-线程安全原理简单分析)
+- [3 数组类型原子类](#3-数组类型原子类)
+ - [3.1 数组类型原子类介绍](#31-数组类型原子类介绍)
+ - [3.2 AtomicIntegerArray 常见方法使用](#32-atomicintegerarray-常见方法使用)
+- [4 引用类型原子类](#4-引用类型原子类)
+ - [4.1 引用类型原子类介绍](#41--引用类型原子类介绍)
+ - [4.2 AtomicReference 类使用示例](#42-atomicreference-类使用示例)
+ - [4.3 AtomicStampedReference 类使用示例](#43-atomicstampedreference-类使用示例)
+ - [4.4 AtomicMarkableReference 类使用示例](#44-atomicmarkablereference-类使用示例)
+- [5 对象的属性修改类型原子类](#5-对象的属性修改类型原子类)
+ - [5.1 对象的属性修改类型原子类介绍](#51-对象的属性修改类型原子类介绍)
+ - [5.2 AtomicIntegerFieldUpdater 类使用示例](#52-atomicintegerfieldupdater-类使用示例)
+
+
### 1 Atomic 原子类介绍
@@ -12,7 +32,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
-
+
根据操作的数据类型,可以将JUC包中的原子类分为4类
@@ -36,14 +56,136 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
**引用类型**
- AtomicReference:引用类型原子类
-- AtomicStampedRerence:原子更新引用类型里的字段原子类
-- AtomicMarkableReference :原子更新带有标记位的引用类型
+- AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,~~也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。~~
+- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
**对象的属性修改类型**
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
-- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
+- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
+
+> 修正: **AtomicMarkableReference 不能解决ABA问题** **[issue#626](https://github.com/Snailclimb/JavaGuide/issues/626)**
+
+```java
+ /**
+
+AtomicMarkableReference是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两个,true和false,
+
+修改的时候在这两个版本号之间来回切换,这样做并不能解决ABA的问题,只是会降低ABA问题发生的几率而已
+
+@author : mazh
+
+@Date : 2020/1/17 14:41
+*/
+
+public class SolveABAByAtomicMarkableReference {
+
+ private static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100, false);
+
+ public static void main(String[] args) {
+
+ Thread refT1 = new Thread(() -> {
+ try {
+ TimeUnit.SECONDS.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ atomicMarkableReference.compareAndSet(100, 101, atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
+ atomicMarkableReference.compareAndSet(101, 100, atomicMarkableReference.isMarked(), !atomicMarkableReference.isMarked());
+ });
+
+ Thread refT2 = new Thread(() -> {
+ boolean marked = atomicMarkableReference.isMarked();
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ boolean c3 = atomicMarkableReference.compareAndSet(100, 101, marked, !marked);
+ System.out.println(c3); // 返回true,实际应该返回false
+ });
+
+ refT1.start();
+ refT2.start();
+ }
+ }
+```
+
+**CAS ABA 问题**
+- 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。
+- 例子描述(可能不太合适,但好理解): 年初,现金为零,然后通过正常劳动赚了三百万,之后正常消费了(比如买房子)三百万。年末,虽然现金零收入(可能变成其他形式了),但是赚了钱是事实,还是得交税的!
+- 代码例子(以``` AtomicInteger ```为例)
+
+```java
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AtomicIntegerDefectDemo {
+ public static void main(String[] args) {
+ defectOfABA();
+ }
+
+ static void defectOfABA() {
+ final AtomicInteger atomicInteger = new AtomicInteger(1);
+
+ Thread coreThread = new Thread(
+ () -> {
+ final int currentValue = atomicInteger.get();
+ System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue);
+
+ // 这段目的:模拟处理其他业务花费的时间
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ boolean casResult = atomicInteger.compareAndSet(1, 2);
+ System.out.println(Thread.currentThread().getName()
+ + " ------ currentValue=" + currentValue
+ + ", finalValue=" + atomicInteger.get()
+ + ", compareAndSet Result=" + casResult);
+ }
+ );
+ coreThread.start();
+
+ // 这段目的:为了让 coreThread 线程先跑起来
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Thread amateurThread = new Thread(
+ () -> {
+ int currentValue = atomicInteger.get();
+ boolean casResult = atomicInteger.compareAndSet(1, 2);
+ System.out.println(Thread.currentThread().getName()
+ + " ------ currentValue=" + currentValue
+ + ", finalValue=" + atomicInteger.get()
+ + ", compareAndSet Result=" + casResult);
+
+ currentValue = atomicInteger.get();
+ casResult = atomicInteger.compareAndSet(2, 1);
+ System.out.println(Thread.currentThread().getName()
+ + " ------ currentValue=" + currentValue
+ + ", finalValue=" + atomicInteger.get()
+ + ", compareAndSet Result=" + casResult);
+ }
+ );
+ amateurThread.start();
+ }
+}
+```
+
+输出内容如下:
+
+```
+Thread-0 ------ currentValue=1
+Thread-1 ------ currentValue=1, finalValue=2, compareAndSet Result=true
+Thread-1 ------ currentValue=2, finalValue=1, compareAndSet Result=true
+Thread-0 ------ currentValue=1, finalValue=2, compareAndSet Result=true
+```
下面我们来详细介绍一下这些原子类。
@@ -60,7 +202,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
**AtomicInteger 类常用方法**
-
+
```java
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
@@ -149,7 +291,7 @@ AtomicInteger 类的部分源码:
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
-CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
+CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
### 3 数组类型原子类
@@ -210,8 +352,8 @@ public class AtomicIntegerArrayTest {
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。
- AtomicReference:引用类型原子类
-- AtomicStampedRerence:原子更新引用类型里的字段原子类
-- AtomicMarkableReference :原子更新带有标记位的引用类型
+- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
+- AtomicMarkableReference :原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,~~也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。~~
上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
@@ -262,13 +404,127 @@ class Person {
}
```
-上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
+上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
```
Daisy
20
```
+#### 4.3 AtomicStampedReference 类使用示例
+```java
+import java.util.concurrent.atomic.AtomicStampedReference;
+
+public class AtomicStampedReferenceDemo {
+ public static void main(String[] args) {
+ // 实例化、取当前值和 stamp 值
+ final Integer initialRef = 0, initialStamp = 0;
+ final AtomicStampedReference asr = new AtomicStampedReference<>(initialRef, initialStamp);
+ System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
+
+ // compare and set
+ final Integer newReference = 666, newStamp = 999;
+ final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
+ System.out.println("currentValue=" + asr.getReference()
+ + ", currentStamp=" + asr.getStamp()
+ + ", casResult=" + casResult);
+
+ // 获取当前的值和当前的 stamp 值
+ int[] arr = new int[1];
+ final Integer currentValue = asr.get(arr);
+ final int currentStamp = arr[0];
+ System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
+
+ // 单独设置 stamp 值
+ final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
+ System.out.println("currentValue=" + asr.getReference()
+ + ", currentStamp=" + asr.getStamp()
+ + ", attemptStampResult=" + attemptStampResult);
+
+ // 重新设置当前值和 stamp 值
+ asr.set(initialRef, initialStamp);
+ System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
+
+ // [不推荐使用,除非搞清楚注释的意思了] weak compare and set
+ // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
+ // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
+ // so is only rarely an appropriate alternative to compareAndSet."
+ // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
+ final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);
+ System.out.println("currentValue=" + asr.getReference()
+ + ", currentStamp=" + asr.getStamp()
+ + ", wCasResult=" + wCasResult);
+ }
+}
+```
+
+输出结果如下:
+```
+currentValue=0, currentStamp=0
+currentValue=666, currentStamp=999, casResult=true
+currentValue=666, currentStamp=999
+currentValue=666, currentStamp=88, attemptStampResult=true
+currentValue=0, currentStamp=0
+currentValue=666, currentStamp=999, wCasResult=true
+```
+
+#### 4.4 AtomicMarkableReference 类使用示例
+
+``` java
+import java.util.concurrent.atomic.AtomicMarkableReference;
+
+public class AtomicMarkableReferenceDemo {
+ public static void main(String[] args) {
+ // 实例化、取当前值和 mark 值
+ final Boolean initialRef = null, initialMark = false;
+ final AtomicMarkableReference amr = new AtomicMarkableReference<>(initialRef, initialMark);
+ System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
+
+ // compare and set
+ final Boolean newReference1 = true, newMark1 = true;
+ final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);
+ System.out.println("currentValue=" + amr.getReference()
+ + ", currentMark=" + amr.isMarked()
+ + ", casResult=" + casResult);
+
+ // 获取当前的值和当前的 mark 值
+ boolean[] arr = new boolean[1];
+ final Boolean currentValue = amr.get(arr);
+ final boolean currentMark = arr[0];
+ System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
+
+ // 单独设置 mark 值
+ final boolean attemptMarkResult = amr.attemptMark(newReference1, false);
+ System.out.println("currentValue=" + amr.getReference()
+ + ", currentMark=" + amr.isMarked()
+ + ", attemptMarkResult=" + attemptMarkResult);
+
+ // 重新设置当前值和 mark 值
+ amr.set(initialRef, initialMark);
+ System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
+
+ // [不推荐使用,除非搞清楚注释的意思了] weak compare and set
+ // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
+ // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
+ // so is only rarely an appropriate alternative to compareAndSet."
+ // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
+ final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);
+ System.out.println("currentValue=" + amr.getReference()
+ + ", currentMark=" + amr.isMarked()
+ + ", wCasResult=" + wCasResult);
+ }
+}
+```
+
+输出结果如下:
+```
+currentValue=null, currentMark=false
+currentValue=true, currentMark=true, casResult=true
+currentValue=true, currentMark=true
+currentValue=true, currentMark=false, attemptMarkResult=true
+currentValue=null, currentMark=false
+currentValue=true, currentMark=true, wCasResult=true
+```
### 5 对象的属性修改类型原子类
@@ -278,7 +534,7 @@ Daisy
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater:原子更新长整形字段的更新器
-- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
+- AtomicReferenceFieldUpdater :原子更新引用类型里的字段的更新器
要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
@@ -335,3 +591,16 @@ class User {
23
```
+## Reference
+
+- 《Java并发编程的艺术》
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
\ No newline at end of file
diff --git a/docs/java/Multithread/BATJ都爱问的多线程面试题.md b/docs/java/Multithread/BATJ都爱问的多线程面试题.md
deleted file mode 100644
index 10d6177b..00000000
--- a/docs/java/Multithread/BATJ都爱问的多线程面试题.md
+++ /dev/null
@@ -1,429 +0,0 @@
-
-
-
-# 一 面试中关于 synchronized 关键字的 5 连击
-
-### 1.1 说一说自己对于 synchronized 关键字的了解
-
-synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
-
-另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
-
-
-### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
-
-**synchronized关键字最主要的三种使用方式:**
-
-- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁**
-- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
-- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
-
-下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
-
-面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
-
-
-
-**双重校验锁实现对象单例(线程安全)**
-
-```java
-public class Singleton {
-
- private volatile static Singleton uniqueInstance;
-
- private Singleton() {
- }
-
- public static Singleton getUniqueInstance() {
- //先判断对象是否已经实例过,没有实例化过才进入加锁代码
- if (uniqueInstance == null) {
- //类对象加锁
- synchronized (Singleton.class) {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
-}
-```
-另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
-
-uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
-
-1. 为 uniqueInstance 分配内存空间
-2. 初始化 uniqueInstance
-3. 将 uniqueInstance 指向分配的内存地址
-
-但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
-
-使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
-
-### 1.3 讲一下 synchronized 关键字的底层原理
-
-**synchronized 关键字底层原理属于 JVM 层面。**
-
-**① synchronized 同步语句块的情况**
-
-```java
-public class SynchronizedDemo {
- public void method() {
- synchronized (this) {
- System.out.println("synchronized 代码块");
- }
- }
-}
-
-```
-
-通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。
-
-
-
-从上面我们可以看出:
-
-**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
-
-**② synchronized 修饰方法的的情况**
-
-```java
-public class SynchronizedDemo2 {
- public synchronized void method() {
- System.out.println("synchronized 方法");
- }
-}
-
-```
-
-
-
-synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
-
-
-### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
-
-JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
-
-锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
-
-关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd)
-
-### 1.5 谈谈 synchronized和ReenTrantLock 的区别
-
-
-**① 两者都是可重入锁**
-
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
-
-**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**
-
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-
-**③ ReenTrantLock 比 synchronized 增加了一些高级功能**
-
-相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
-
-- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
-
-如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
-
-**④ 性能已不是选择标准**
-
-# 二 面试中关于线程池的 4 连击
-
-### 2.1 讲一下Java内存模型
-
-
-在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量 ,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存** (比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致** 。
-
-
-
-要解决这个问题,就需要把变量声明为 **volatile** ,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
-
-说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
-
-
-
-
-### 2.2 说说 synchronized 关键字和 volatile 关键字的区别
-
- synchronized关键字和volatile关键字比较
-
-- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。
-- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞**
-- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。**
-- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。**
-
-
-# 三 面试中关于 线程池的 2 连击
-
-
-### 3.1 为什么要用线程池?
-
-线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
-
-这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处:
-
-- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
-
-### 3.2 实现Runnable接口和Callable接口的区别
-
-如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
-
- **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callable(Runnable task)`或`Executors.callable(Runnable task,Object resule)`)。
-
-### 3.3 执行execute()方法和submit()方法的区别是什么呢?
-
- 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
-
- 2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
-
-
-### 3.4 如何创建线程池
-
-《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
-
-> Executors 返回线程池对象的弊端如下:
->
-> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-**方式一:通过构造方法实现**
-
-**方式二:通过Executor 框架的工具类Executors来实现**
-我们可以创建三种类型的ThreadPoolExecutor:
-
-- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
-- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
-- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
-
-对应Executors工具类中的方法如图所示:
-
-
-
-# 四 面试中关于 Atomic 原子类的 4 连击
-
-### 4.1 介绍一下Atomic 原子类
-
-Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
-
-所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
-
-
-并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
-
-
-
-### 4.2 JUC 包中的原子类是哪4类?
-
-**基本类型**
-
-使用原子的方式更新基本类型
-
-- AtomicInteger:整形原子类
-- AtomicLong:长整型原子类
-- AtomicBoolean :布尔型原子类
-
-**数组类型**
-
-使用原子的方式更新数组里的某个元素
-
-
-- AtomicIntegerArray:整形数组原子类
-- AtomicLongArray:长整形数组原子类
-- AtomicReferenceArray :引用类型数组原子类
-
-**引用类型**
-
-- AtomicReference:引用类型原子类
-- AtomicStampedRerence:原子更新引用类型里的字段原子类
-- AtomicMarkableReference :原子更新带有标记位的引用类型
-
-**对象的属性修改类型**
-
-- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
-- AtomicLongFieldUpdater:原子更新长整形字段的更新器
-- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
-
-
-### 4.3 讲讲 AtomicInteger 的使用
-
- **AtomicInteger 类常用方法**
-
-```java
-public final int get() //获取当前的值
-public final int getAndSet(int newValue)//获取当前的值,并设置新的值
-public final int getAndIncrement()//获取当前的值,并自增
-public final int getAndDecrement() //获取当前的值,并自减
-public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
-boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
-public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
-```
-
- **AtomicInteger 类的使用示例**
-
-使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。
-```java
-class AtomicIntegerTest {
- private AtomicInteger count = new AtomicInteger();
- //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
- public void increment() {
- count.incrementAndGet();
- }
-
- public int getCount() {
- return count.get();
- }
-}
-
-```
-
-### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理
-
-AtomicInteger 线程安全原理简单分析
-
-AtomicInteger 类的部分源码:
-
-```java
- // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private static final long valueOffset;
-
- static {
- try {
- valueOffset = unsafe.objectFieldOffset
- (AtomicInteger.class.getDeclaredField("value"));
- } catch (Exception ex) { throw new Error(ex); }
- }
-
- private volatile int value;
-```
-
-AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
-
-CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
-
-关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
-
-# 五 AQS
-
-### 5.1 AQS 介绍
-
-AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
-
-
-
-AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
-
-### 5.2 AQS 原理分析
-
-AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。
-
-> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
-
-下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
-
-#### 5.2.1 AQS 原理概览
-
-
-
-**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
-
-> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
-
-看个AQS(AbstractQueuedSynchronizer)原理图:
-
-
-
-
-AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
-
-```java
-private volatile int state;//共享变量,使用volatile修饰保证线程可见性
-```
-
-状态信息通过procted类型的getState,setState,compareAndSetState进行操作
-
-```java
-
-//返回同步状态的当前值
-protected final int getState() {
- return state;
-}
- // 设置同步状态的值
-protected final void setState(int newState) {
- state = newState;
-}
-//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
-protected final boolean compareAndSetState(int expect, int update) {
- return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
-}
-```
-
-#### 5.2.2 AQS 对资源的共享方式
-
-**AQS定义两种资源共享方式**
-
-- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
-- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
-
-ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
-
-不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
-
-#### 5.2.3 AQS底层使用了模板方法模式
-
-同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
-
-1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
-2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
-
-这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
-
-**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:**
-
-```java
-isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
-tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
-tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
-tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
-
-```
-
-默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
-
-以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
-
-再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
-
-一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
-
-推荐两篇 AQS 原理和相关源码分析的文章:
-
-- http://www.cnblogs.com/waterystone/p/4920797.html
-- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
-
-### 5.3 AQS 组件总结
-
-- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
-- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
-- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
-
-关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
-
-# Reference
-
-- 《深入理解 Java 虚拟机》
-- 《实战 Java 高并发程序设计》
-- 《Java并发编程的艺术》
-- http://www.cnblogs.com/waterystone/p/4920797.html
-- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
diff --git a/docs/java/Multithread/ConcurrentProgramming1-并发编程基础知识.md b/docs/java/Multithread/ConcurrentProgramming1-并发编程基础知识.md
deleted file mode 100644
index 0ae72071..00000000
--- a/docs/java/Multithread/ConcurrentProgramming1-并发编程基础知识.md
+++ /dev/null
@@ -1,269 +0,0 @@
-# Java 并发基础知识
-
-Java 并发的基础知识,可能会在笔试中遇到,技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你:“谈谈自己对于进程和线程的理解,两者的区别是什么?”
-
-**本节思维导图:**
-
-
-
-## 一 进程和线程
-
-进程和线程的对比这一知识点由于过于基础,所以在面试中很少碰到,但是极有可能会在笔试题中碰到。
-
-常见的提问形式是这样的:**“什么是线程和进程?,请简要描述线程与进程的关系、区别及优缺点? ”**。
-
-### 1.1. 何为进程?
-
-进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
-
-在Java中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
-
-如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。
-
-
-
-### 1.2 何为线程?
-
-线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
-
-```java
-public class MultiThread {
- public static void main(String[] args) {
- // 获取Java线程管理MXBean
- ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
- // 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
- ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
- // 遍历线程信息,仅打印线程ID和线程名称信息
- for (ThreadInfo threadInfo : threadInfos) {
- System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
- }
- }
-}
-```
-
-上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行main方法即可):
-
-```
-[5] Attach Listener //添加事件
-[4] Signal Dispatcher // 分发处理给JVM信号的线程
-[3] Finalizer //调用对象finalize方法的线程
-[2] Reference Handler //清除reference线程
-[1] main //main线程,程序入口
-```
-
-从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
-
-### 1.3 从 JVM 角度说进程和线程之间的关系(重要)
-
-#### 1.3.1 图解进程和线程的关系
-
-下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域(运行时数据区)这部分知识不太了解的话可以阅读一下我的这篇文章:[《可能是把Java内存区域讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md)
-
-
-
-从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
-
-下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
-
-#### 1.3.2 程序计数器为什么是私有的?
-
-程序计数器主要有下面两个作用:
-
-1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
-2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
-
-需要注意的是,如果执行的是native方法,那么程序计数器记录的是undefined地址,只有执行的是Java代码时程序计数器记录的才是下一条指令的地址。
-
-所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
-
-#### 1.3.3 虚拟机栈和本地方法栈为什么是私有的?
-
-- **虚拟机栈:**每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
-- **本地方法栈:**和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
-
-所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
-
-#### 1.3.4 一句话简单了解堆和方法区
-
-堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
-
-## 二 多线程并发编程
-
-### 2.1 并发与并行
-
-- **并发:** 同一时间段,多个任务都在执行(单位时间内不一定同时执行);
-- **并行:**单位时间内,多个任务同时执行。
-
-### 2.1 多线程并发编程详解
-
-单CPU时代多个任务共享一个CPU,某一特定时刻只能有一个任务被执行,CPU会分配时间片给当前要执行的任务。当一个任务占用CPU时,其他任务就会被挂起。当占用CPU的任务的时间片用完后,才会由 CPU 选择下一个需要执行的任务。所以说,在单核CPU时代,多线程编程没有太大意义,反而会因为线程间频繁的上下文切换而带来额外开销。
-
-但现在 CPU 一般都是多核,如果这个CPU是多核的话,那么进程中的不同线程可以使用不同核心,实现了真正意义上的并行运行。**那为什么我们不直接叫做多线程并行编程呢?**
-
-**这是因为多线程在实际开发使用中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。`**
-
-### 2.2 为什么要多线程并发编程?
-
-- **从计算机底层来说:**线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
-
-- **从当代互联网发展趋势来说:**现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
-
-## 三 线程的创建与运行
-
-前两种实际上很少使用,一般都是用线程池的方式比较多一点。
-
-### 3.1 继承 Thread 类的方式
-
-
-```java
-public class MyThread extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println("MyThread");
- }
-}
-```
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- MyThread mythread = new MyThread();
- mythread.start();
- System.out.println("运行结束");
- }
-
-}
-
-```
-运行结果:
-
-
-从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
-
-### 3.2 实现Runnable接口的方式
-
-推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。
-
-MyRunnable.java
-
-```java
-public class MyRunnable implements Runnable {
- @Override
- public void run() {
- System.out.println("MyRunnable");
- }
-}
-```
-
-Run.java
-
-```java
-public class Run {
-
- public static void main(String[] args) {
- Runnable runnable=new MyRunnable();
- Thread thread=new Thread(runnable);
- thread.start();
- System.out.println("运行结束!");
- }
-
-}
-```
-运行结果:
-
-
-### 3.3 使用线程池的方式
-
-使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。这里就不给大家演示代码了,线程池这一节会详细介绍到这部分内容。
-
-## 四 线程的生命周期和状态
-
-Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。
-
-
-
-线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节):
-
-
-
-
-
-由上图可以看出:
-
-线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
-
-> 操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
-
-
-
-当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。
-
-## 五 线程优先级
-
-**理论上**来说系统会根据优先级来决定首先使哪个线程进入运行状态。当 CPU 比较闲的时候,设置线程优先级几乎不会有任何作用,而且很多操作系统压根不会不会理会你设置的线程优先级,所以不要让业务过度依赖于线程的优先级。
-
-另外,**线程优先级具有继承特性**比如A线程启动B线程,则B线程的优先级和A是一样的。**线程优先级还具有随机性** 也就是说线程优先级高的不一定每一次都先执行完。
-
-Thread类中包含的成员变量代表了线程的某些优先级。如**Thread.MIN_PRIORITY(常数1)**,**Thread.NORM_PRIORITY(常数5)**,**Thread.MAX_PRIORITY(常数10)**。其中每个线程的优先级都在**1** 到**10** 之间,在默认情况下优先级都是**Thread.NORM_PRIORITY(常数5)**。
-
-**一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了。**
-
-**相关方法:**
-
-```java
-public final void setPriority(int newPriority) //为线程设定优先级
-public final int getPriority() //获取线程的优先级
-```
-**设置线程优先级方法源码:**
-
-```java
- public final void setPriority(int newPriority) {
- ThreadGroup g;
- checkAccess();
- //线程游戏优先级不能小于1也不能大于10,否则会抛出异常
- if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
- throw new IllegalArgumentException();
- }
- //如果指定的线程优先级大于该线程所在线程组的最大优先级,那么该线程的优先级将设为线程组的最大优先级
- if((g = getThreadGroup()) != null) {
- if (newPriority > g.getMaxPriority()) {
- newPriority = g.getMaxPriority();
- }
- setPriority0(priority = newPriority);
- }
- }
-
-```
-
-## 六 守护线程和用户线程
-
-**守护线程和用户线程简介:**
-
-- **用户(User)线程:**运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
-- **守护(Daemon)线程:**运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 **“佣人”**。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作.
-
-main 函数所在的线程就是一个用户线程啊,main函数启动的同时在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。
-
-**那么守护线程和用户线程有什么区别呢?**
-
-比较明显的区别之一是用户线程结束,JVM退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。
-
-**注意事项:**
-
-1. `setDaemon(true)`必须在`start()`方法前执行,否则会抛出 `IllegalThreadStateException` 异常
-2. 在守护线程中产生的新线程也是守护线程
-3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
-4. 守护(Daemon)线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作,所以守护(Daemon)线程中的finally语句块可能无法被执行。
-
-
-
-## 参考
-
-- 《Java并发编程之美》
-- 《Java并发编程的艺术》
-- https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/
\ No newline at end of file
diff --git a/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md b/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md
new file mode 100644
index 00000000..7a692cf1
--- /dev/null
+++ b/docs/java/Multithread/JavaConcurrencyAdvancedCommonInterviewQuestions.md
@@ -0,0 +1,940 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [Java 并发进阶常见面试题总结](#java-并发进阶常见面试题总结)
+ - [1. synchronized 关键字](#1-synchronized-关键字)
+ - [1.1. 说一说自己对于 synchronized 关键字的了解](#11-说一说自己对于-synchronized-关键字的了解)
+ - [1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗](#12-说说自己是怎么使用-synchronized-关键字在项目中用到了吗)
+ - [1.3. 讲一下 synchronized 关键字的底层原理](#13-讲一下-synchronized-关键字的底层原理)
+ - [1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗](#14-说说-jdk16-之后的synchronized-关键字底层做了哪些优化可以详细介绍一下这些优化吗)
+ - [1.5. 谈谈 synchronized和ReentrantLock 的区别](#15-谈谈-synchronized和reentrantlock-的区别)
+ - [2. volatile关键字](#2-volatile关键字)
+ - [2.1. 讲一下Java内存模型](#21-讲一下java内存模型)
+ - [2.2. 说说 synchronized 关键字和 volatile 关键字的区别](#22-说说-synchronized-关键字和-volatile-关键字的区别)
+ - [3. ThreadLocal](#3-threadlocal)
+ - [3.1. ThreadLocal简介](#31-threadlocal简介)
+ - [3.2. ThreadLocal示例](#32-threadlocal示例)
+ - [3.3. ThreadLocal原理](#33-threadlocal原理)
+ - [3.4. ThreadLocal 内存泄露问题](#34-threadlocal-内存泄露问题)
+ - [4. 线程池](#4-线程池)
+ - [4.1. 为什么要用线程池?](#41-为什么要用线程池)
+ - [4.2. 实现Runnable接口和Callable接口的区别](#42-实现runnable接口和callable接口的区别)
+ - [4.3. 执行execute()方法和submit()方法的区别是什么呢?](#43-执行execute方法和submit方法的区别是什么呢)
+ - [4.4. 如何创建线程池](#44-如何创建线程池)
+ - [5. Atomic 原子类](#5-atomic-原子类)
+ - [5.1. 介绍一下Atomic 原子类](#51-介绍一下atomic-原子类)
+ - [5.2. JUC 包中的原子类是哪4类?](#52-juc-包中的原子类是哪4类)
+ - [5.3. 讲讲 AtomicInteger 的使用](#53-讲讲-atomicinteger-的使用)
+ - [5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理](#54-能不能给我简单介绍一下-atomicinteger-类的原理)
+ - [6. AQS](#6-aqs)
+ - [6.1. AQS 介绍](#61-aqs-介绍)
+ - [6.2. AQS 原理分析](#62-aqs-原理分析)
+ - [6.2.1. AQS 原理概览](#621-aqs-原理概览)
+ - [6.2.2. AQS 对资源的共享方式](#622-aqs-对资源的共享方式)
+ - [6.2.3. AQS底层使用了模板方法模式](#623-aqs底层使用了模板方法模式)
+ - [6.3. AQS 组件总结](#63-aqs-组件总结)
+ - [7 Reference](#7-reference)
+
+
+
+# Java 并发进阶常见面试题总结
+
+## 1. synchronized 关键字
+
+### 1.1. 说一说自己对于 synchronized 关键字的了解
+
+synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
+
+另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
+
+
+### 1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
+
+**synchronized关键字最主要的三种使用方式:**
+
+- **修饰实例方法:** 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
+- **修饰静态方法:** 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
+- **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
+
+**总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
+
+下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
+
+面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
+
+**双重校验锁实现对象单例(线程安全)**
+
+```java
+public class Singleton {
+
+ private volatile static Singleton uniqueInstance;
+
+ private Singleton() {
+ }
+
+ public static Singleton getUniqueInstance() {
+ //先判断对象是否已经实例过,没有实例化过才进入加锁代码
+ if (uniqueInstance == null) {
+ //类对象加锁
+ synchronized (Singleton.class) {
+ if (uniqueInstance == null) {
+ uniqueInstance = new Singleton();
+ }
+ }
+ }
+ return uniqueInstance;
+ }
+}
+```
+另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
+
+uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
+
+1. 为 uniqueInstance 分配内存空间
+2. 初始化 uniqueInstance
+3. 将 uniqueInstance 指向分配的内存地址
+
+但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
+
+使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
+
+### 1.3. 讲一下 synchronized 关键字的底层原理
+
+**synchronized 关键字底层原理属于 JVM 层面。**
+
+**① synchronized 同步语句块的情况**
+
+```java
+public class SynchronizedDemo {
+ public void method() {
+ synchronized (this) {
+ System.out.println("synchronized 代码块");
+ }
+ }
+}
+
+```
+
+通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。
+
+
+
+从上面我们可以看出:
+
+**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
+
+**② synchronized 修饰方法的的情况**
+
+```java
+public class SynchronizedDemo2 {
+ public synchronized void method() {
+ System.out.println("synchronized 方法");
+ }
+}
+
+```
+
+
+
+synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
+
+
+### 1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
+
+JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
+
+锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
+
+关于这几种优化的详细信息可以查看笔主的这篇文章:
+
+### 1.5. 谈谈 synchronized和ReentrantLock 的区别
+
+
+**① 两者都是可重入锁**
+
+两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
+
+**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
+
+synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
+
+**③ ReentrantLock 比 synchronized 增加了一些高级功能**
+
+相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)**
+
+- **ReentrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
+- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
+- synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
+
+如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。
+
+**④ 性能已不是选择标准**
+
+## 2. volatile关键字
+
+### 2.1. 讲一下Java内存模型
+
+
+在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。
+
+
+
+要解决这个问题,就需要把变量声明为**volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
+
+说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
+
+
+
+### 2.2 并发编程的三个重要特性
+
+1. **原子性** : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。`synchronized ` 可以保证代码片段的原子性。
+2. **可见性** :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。`volatile` 关键字可以保证共享变量的可见性。
+3. **有序性** :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。`volatile` 关键字可以禁止指令进行重排序优化。
+
+### 2.3. 说说 synchronized 关键字和 volatile 关键字的区别
+
+`synchronized` 关键字和 `volatile` 关键字是两个互补的存在,而不是对立的存在:
+
+- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。
+- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞**
+- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。**
+- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。**
+
+## 3. ThreadLocal
+
+### 3.1. ThreadLocal简介
+
+通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
+
+**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
+
+再举个简单的例子:
+
+比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来避免这两个线程竞争的。
+
+### 3.2. ThreadLocal示例
+
+相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
+
+```java
+import java.text.SimpleDateFormat;
+import java.util.Random;
+
+public class ThreadLocalExample implements Runnable{
+
+ // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
+ private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
+
+ public static void main(String[] args) throws InterruptedException {
+ ThreadLocalExample obj = new ThreadLocalExample();
+ for(int i=0 ; i<10; i++){
+ Thread t = new Thread(obj, ""+i);
+ Thread.sleep(new Random().nextInt(1000));
+ t.start();
+ }
+ }
+
+ @Override
+ public void run() {
+ System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
+ try {
+ Thread.sleep(new Random().nextInt(1000));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ //formatter pattern is changed here by thread, but it won't reflect to other threads
+ formatter.set(new SimpleDateFormat());
+
+ System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
+ }
+
+}
+
+```
+
+Output:
+
+```
+Thread Name= 0 default Formatter = yyyyMMdd HHmm
+Thread Name= 0 formatter = yy-M-d ah:mm
+Thread Name= 1 default Formatter = yyyyMMdd HHmm
+Thread Name= 2 default Formatter = yyyyMMdd HHmm
+Thread Name= 1 formatter = yy-M-d ah:mm
+Thread Name= 3 default Formatter = yyyyMMdd HHmm
+Thread Name= 2 formatter = yy-M-d ah:mm
+Thread Name= 4 default Formatter = yyyyMMdd HHmm
+Thread Name= 3 formatter = yy-M-d ah:mm
+Thread Name= 4 formatter = yy-M-d ah:mm
+Thread Name= 5 default Formatter = yyyyMMdd HHmm
+Thread Name= 5 formatter = yy-M-d ah:mm
+Thread Name= 6 default Formatter = yyyyMMdd HHmm
+Thread Name= 6 formatter = yy-M-d ah:mm
+Thread Name= 7 default Formatter = yyyyMMdd HHmm
+Thread Name= 7 formatter = yy-M-d ah:mm
+Thread Name= 8 default Formatter = yyyyMMdd HHmm
+Thread Name= 9 default Formatter = yyyyMMdd HHmm
+Thread Name= 8 formatter = yy-M-d ah:mm
+Thread Name= 9 formatter = yy-M-d ah:mm
+```
+
+从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。
+
+上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。
+
+```java
+ private static final ThreadLocal formatter = new ThreadLocal(){
+ @Override
+ protected SimpleDateFormat initialValue()
+ {
+ return new SimpleDateFormat("yyyyMMdd HHmm");
+ }
+ };
+```
+
+### 3.3. ThreadLocal原理
+
+从 `Thread`类源代码入手。
+
+```java
+public class Thread implements Runnable {
+ ......
+//与此线程有关的ThreadLocal值。由ThreadLocal类维护
+ThreadLocal.ThreadLocalMap threadLocals = null;
+
+//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
+ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
+ ......
+}
+```
+
+从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。
+
+`ThreadLocal`类的`set()`方法
+
+```java
+ public void set(T value) {
+ Thread t = Thread.currentThread();
+ ThreadLocalMap map = getMap(t);
+ if (map != null)
+ map.set(this, value);
+ else
+ createMap(t, value);
+ }
+ ThreadLocalMap getMap(Thread t) {
+ return t.threadLocals;
+ }
+```
+
+通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。
+
+**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key ,Object 对象为 value的键值对。**
+
+```java
+ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
+ ......
+}
+```
+
+比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。
+
+
+
+`ThreadLocalMap`是`ThreadLocal`的静态内部类。
+
+
+
+### 3.4. ThreadLocal 内存泄露问题
+
+`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
+
+```java
+ static class Entry extends WeakReference> {
+ /** The value associated with this ThreadLocal. */
+ Object value;
+
+ Entry(ThreadLocal> k, Object v) {
+ super(k);
+ value = v;
+ }
+ }
+```
+
+**弱引用介绍:**
+
+> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
+>
+> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
+
+## 4. 线程池
+
+### 4.1. 为什么要用线程池?
+
+> **池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。**
+
+**线程池**提供了一种限制和管理资源(包括执行一个任务)。 每个**线程池**还维护一些基本统计信息,例如已完成任务的数量。
+
+这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**:
+
+- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
+- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
+- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
+
+### 4.2. 实现Runnable接口和Callable接口的区别
+
+`Runnable`自Java 1.0以来一直存在,但`Callable`仅在Java 1.5中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。
+
+工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
+
+`Runnable.java`
+
+```java
+@FunctionalInterface
+public interface Runnable {
+ /**
+ * 被线程执行,没有返回值也无法抛出异常
+ */
+ public abstract void run();
+}
+```
+
+`Callable.java`
+
+```java
+@FunctionalInterface
+public interface Callable {
+ /**
+ * 计算结果,或在无法这样做时抛出异常。
+ * @return 计算得出的结果
+ * @throws 如果无法计算结果,则抛出异常
+ */
+ V call() throws Exception;
+}
+```
+
+### 4.3. 执行execute()方法和submit()方法的区别是什么呢?
+
+1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
+2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
+
+我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码:
+
+```java
+ public Future> submit(Runnable task) {
+ if (task == null) throw new NullPointerException();
+ RunnableFuture ftask = newTaskFor(task, null);
+ execute(ftask);
+ return ftask;
+ }
+```
+
+上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
+
+```java
+ protected RunnableFuture newTaskFor(Runnable runnable, T value) {
+ return new FutureTask(runnable, value);
+ }
+```
+
+我们再来看看`execute()`方法:
+
+```java
+ public void execute(Runnable command) {
+ ...
+ }
+```
+
+### 4.4. 如何创建线程池
+
+《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
+
+> Executors 返回线程池对象的弊端如下:
+>
+> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
+> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
+
+**方式一:通过构造方法实现**
+
+**方式二:通过Executor 框架的工具类Executors来实现**
+我们可以创建三种类型的ThreadPoolExecutor:
+
+- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
+- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
+- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
+
+对应Executors工具类中的方法如图所示:
+
+
+### 4.5 ThreadPoolExecutor 类分析
+
+`ThreadPoolExecutor` 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。
+
+```java
+ /**
+ * 用给定的初始参数创建一个新的ThreadPoolExecutor。
+ */
+ public ThreadPoolExecutor(int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ BlockingQueue workQueue,
+ ThreadFactory threadFactory,
+ RejectedExecutionHandler handler) {
+ if (corePoolSize < 0 ||
+ maximumPoolSize <= 0 ||
+ maximumPoolSize < corePoolSize ||
+ keepAliveTime < 0)
+ throw new IllegalArgumentException();
+ if (workQueue == null || threadFactory == null || handler == null)
+ throw new NullPointerException();
+ this.corePoolSize = corePoolSize;
+ this.maximumPoolSize = maximumPoolSize;
+ this.workQueue = workQueue;
+ this.keepAliveTime = unit.toNanos(keepAliveTime);
+ this.threadFactory = threadFactory;
+ this.handler = handler;
+ }
+```
+
+**下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!所以,务必拿着小本本记清楚。**
+
+#### 4.5.1 `ThreadPoolExecutor`构造函数重要参数分析
+
+**`ThreadPoolExecutor` 3 个最重要的参数:**
+
+- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。
+- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
+- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
+
+`ThreadPoolExecutor`其他常见参数:
+
+1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁;
+2. **`unit`** : `keepAliveTime` 参数的时间单位。
+3. **`threadFactory`** :executor 创建新线程的时候会用到。
+4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。
+
+#### 4.5.2 `ThreadPoolExecutor` 饱和策略
+
+**`ThreadPoolExecutor` 饱和策略定义:**
+
+如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略:
+
+- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。
+- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
+- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。
+- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。
+
+举个例子: Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了)
+
+### 4.6 一个简单的线程池Demo:`Runnable`+`ThreadPoolExecutor`
+
+为了让大家更清楚上面的面试题中的一些概念,我写了一个简单的线程池 Demo。
+
+首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。)
+
+`MyRunnable.java`
+
+```java
+import java.util.Date;
+
+/**
+ * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
+ * @author shuang.kou
+ */
+public class MyRunnable implements Runnable {
+
+ private String command;
+
+ public MyRunnable(String s) {
+ this.command = s;
+ }
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
+ processCommand();
+ System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
+ }
+
+ private void processCommand() {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.command;
+ }
+}
+
+```
+
+编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。
+
+`ThreadPoolExecutorDemo.java`
+
+```java
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPoolExecutorDemo {
+
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAX_POOL_SIZE = 10;
+ private static final int QUEUE_CAPACITY = 100;
+ private static final Long KEEP_ALIVE_TIME = 1L;
+ public static void main(String[] args) {
+
+ //使用阿里巴巴推荐的创建线程池的方式
+ //通过ThreadPoolExecutor构造函数自定义参数创建
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE,
+ MAX_POOL_SIZE,
+ KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(QUEUE_CAPACITY),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ for (int i = 0; i < 10; i++) {
+ //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
+ Runnable worker = new MyRunnable("" + i);
+ //执行Runnable
+ executor.execute(worker);
+ }
+ //终止线程池
+ executor.shutdown();
+ while (!executor.isTerminated()) {
+ }
+ System.out.println("Finished all threads");
+ }
+}
+
+```
+
+可以看到我们上面的代码指定了:
+
+1. `corePoolSize`: 核心线程数为 5。
+2. `maximumPoolSize` :最大线程数 10
+3. `keepAliveTime` : 等待时间为 1L。
+4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。
+5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100;
+6. `handler`:饱和策略为 `CallerRunsPolicy`。
+
+**Output:**
+
+```
+pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019
+pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019
+pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019
+pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019
+pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019
+pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019
+pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019
+pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019
+pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019
+pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019
+pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
+
+```
+
+### 4.7 线程池原理分析
+
+承接 4.6 节,我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)
+
+现在,我们就分析上面的输出内容来简单分析一下线程池原理。
+
+**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:
+
+```java
+ // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
+ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
+
+ private static int workerCountOf(int c) {
+ return c & CAPACITY;
+ }
+
+ private final BlockingQueue workQueue;
+
+ public void execute(Runnable command) {
+ // 如果任务为null,则抛出异常。
+ if (command == null)
+ throw new NullPointerException();
+ // ctl 中保存的线程池当前的一些状态信息
+ int c = ctl.get();
+
+ // 下面会涉及到 3 步 操作
+ // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
+ // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
+ if (workerCountOf(c) < corePoolSize) {
+ if (addWorker(command, true))
+ return;
+ c = ctl.get();
+ }
+ // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
+ // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
+ if (isRunning(c) && workQueue.offer(command)) {
+ int recheck = ctl.get();
+ // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
+ if (!isRunning(recheck) && remove(command))
+ reject(command);
+ // 如果当前线程池为空就新创建一个线程并执行。
+ else if (workerCountOf(recheck) == 0)
+ addWorker(null, false);
+ }
+ //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
+ //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
+ else if (!addWorker(command, false))
+ reject(command);
+ }
+```
+
+通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
+
+
+
+现在,让我们在回到 4.6 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?
+
+没搞懂的话,也没关系,可以看看我的分析:
+
+> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。
+
+## 5. Atomic 原子类
+
+### 5.1. 介绍一下Atomic 原子类
+
+Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
+
+所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
+
+
+并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
+
+
+
+### 5.2. JUC 包中的原子类是哪4类?
+
+**基本类型**
+
+使用原子的方式更新基本类型
+
+- AtomicInteger:整形原子类
+- AtomicLong:长整型原子类
+- AtomicBoolean:布尔型原子类
+
+**数组类型**
+
+使用原子的方式更新数组里的某个元素
+
+
+- AtomicIntegerArray:整形数组原子类
+- AtomicLongArray:长整形数组原子类
+- AtomicReferenceArray:引用类型数组原子类
+
+**引用类型**
+
+- AtomicReference:引用类型原子类
+- AtomicStampedReference:原子更新引用类型里的字段原子类
+- AtomicMarkableReference :原子更新带有标记位的引用类型
+
+**对象的属性修改类型**
+
+- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
+- AtomicLongFieldUpdater:原子更新长整形字段的更新器
+- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
+
+
+### 5.3. 讲讲 AtomicInteger 的使用
+
+ **AtomicInteger 类常用方法**
+
+```java
+public final int get() //获取当前的值
+public final int getAndSet(int newValue)//获取当前的值,并设置新的值
+public final int getAndIncrement()//获取当前的值,并自增
+public final int getAndDecrement() //获取当前的值,并自减
+public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
+boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
+public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
+```
+
+ **AtomicInteger 类的使用示例**
+
+使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。
+```java
+class AtomicIntegerTest {
+ private AtomicInteger count = new AtomicInteger();
+ //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。
+ public void increment() {
+ count.incrementAndGet();
+ }
+
+ public int getCount() {
+ return count.get();
+ }
+}
+
+```
+
+### 5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理
+
+AtomicInteger 线程安全原理简单分析
+
+AtomicInteger 类的部分源码:
+
+```java
+ // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
+ private static final Unsafe unsafe = Unsafe.getUnsafe();
+ private static final long valueOffset;
+
+ static {
+ try {
+ valueOffset = unsafe.objectFieldOffset
+ (AtomicInteger.class.getDeclaredField("value"));
+ } catch (Exception ex) { throw new Error(ex); }
+ }
+
+ private volatile int value;
+```
+
+AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
+
+CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
+
+关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg)
+
+## 6. AQS
+
+### 6.1. AQS 介绍
+
+AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
+
+
+
+AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
+
+### 6.2. AQS 原理分析
+
+AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。
+
+> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。
+
+下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。
+
+#### 6.2.1. AQS 原理概览
+
+**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。**
+
+> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
+
+看个AQS(AbstractQueuedSynchronizer)原理图:
+
+
+
+
+AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
+
+```java
+private volatile int state;//共享变量,使用volatile修饰保证线程可见性
+```
+
+状态信息通过protected类型的getState,setState,compareAndSetState进行操作
+
+```java
+
+//返回同步状态的当前值
+protected final int getState() {
+ return state;
+}
+ // 设置同步状态的值
+protected final void setState(int newState) {
+ state = newState;
+}
+//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
+protected final boolean compareAndSetState(int expect, int update) {
+ return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
+}
+```
+
+#### 6.2.2. AQS 对资源的共享方式
+
+**AQS定义两种资源共享方式**
+
+- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
+ - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
+ - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
+- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
+
+ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。
+
+不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
+
+#### 6.2.3. AQS底层使用了模板方法模式
+
+同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):
+
+1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
+2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
+
+这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。
+
+**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:**
+
+```java
+isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
+tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
+tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
+tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
+tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
+
+```
+
+默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
+
+以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
+
+再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
+
+一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。
+
+推荐两篇 AQS 原理和相关源码分析的文章:
+
+- http://www.cnblogs.com/waterystone/p/4920797.html
+- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
+
+### 6.3. AQS 组件总结
+
+- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
+- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
+- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
+
+## 7 Reference
+
+- 《深入理解 Java 虚拟机》
+- 《实战 Java 高并发程序设计》
+- 《Java并发编程的艺术》
+- http://www.cnblogs.com/waterystone/p/4920797.html
+- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html
+-
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md b/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md
new file mode 100644
index 00000000..39d73dd8
--- /dev/null
+++ b/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md
@@ -0,0 +1,303 @@
+
+
+- [Java 并发基础常见面试题总结](#java-并发基础常见面试题总结)
+ - [1. 什么是线程和进程?](#1-什么是线程和进程)
+ - [1.1. 何为进程?](#11-何为进程)
+ - [1.2. 何为线程?](#12-何为线程)
+ - [2. 请简要描述线程与进程的关系,区别及优缺点?](#2-请简要描述线程与进程的关系区别及优缺点)
+ - [2.1. 图解进程和线程的关系](#21-图解进程和线程的关系)
+ - [2.2. 程序计数器为什么是私有的?](#22-程序计数器为什么是私有的)
+ - [2.3. 虚拟机栈和本地方法栈为什么是私有的?](#23-虚拟机栈和本地方法栈为什么是私有的)
+ - [2.4. 一句话简单了解堆和方法区](#24-一句话简单了解堆和方法区)
+ - [3. 说说并发与并行的区别?](#3-说说并发与并行的区别)
+ - [4. 为什么要使用多线程呢?](#4-为什么要使用多线程呢)
+ - [5. 使用多线程可能带来什么问题?](#5-使用多线程可能带来什么问题)
+ - [6. 说说线程的生命周期和状态?](#6-说说线程的生命周期和状态)
+ - [7. 什么是上下文切换?](#7-什么是上下文切换)
+ - [8. 什么是线程死锁?如何避免死锁?](#8-什么是线程死锁如何避免死锁)
+ - [8.1. 认识线程死锁](#81-认识线程死锁)
+ - [8.2. 如何避免线程死锁?](#82-如何避免线程死锁)
+ - [9. 说说 sleep() 方法和 wait() 方法区别和共同点?](#9-说说-sleep-方法和-wait-方法区别和共同点)
+ - [10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?](#10-为什么我们调用-start-方法时会执行-run-方法为什么我们不能直接调用-run-方法)
+ - [公众号](#公众号)
+
+
+
+# Java 并发基础常见面试题总结
+
+## 1. 什么是线程和进程?
+
+### 1.1. 何为进程?
+
+进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
+
+在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
+
+如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
+
+
+
+### 1.2. 何为线程?
+
+线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
+
+Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
+
+```java
+public class MultiThread {
+ public static void main(String[] args) {
+ // 获取 Java 线程管理 MXBean
+ ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+ // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
+ ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
+ // 遍历线程信息,仅打印线程 ID 和线程名称信息
+ for (ThreadInfo threadInfo : threadInfos) {
+ System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
+ }
+ }
+}
+```
+
+上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
+
+```
+[5] Attach Listener //添加事件
+[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
+[3] Finalizer //调用对象 finalize 方法的线程
+[2] Reference Handler //清除 reference 线程
+[1] main //main 线程,程序入口
+```
+
+从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
+
+## 2. 请简要描述线程与进程的关系,区别及优缺点?
+
+**从 JVM 角度说进程和线程之间的关系**
+
+### 2.1. 图解进程和线程的关系
+
+下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](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 "《可能是把 Java 内存区域讲的最清楚的一篇文章》")
+
+
+
+
+
+从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
+
+**总结:** 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
+
+下面是该知识点的扩展内容!
+
+下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
+
+### 2.2. 程序计数器为什么是私有的?
+
+程序计数器主要有下面两个作用:
+
+1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
+
+需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
+
+所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
+
+### 2.3. 虚拟机栈和本地方法栈为什么是私有的?
+
+- **虚拟机栈:** 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
+- **本地方法栈:** 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
+
+所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
+
+### 2.4. 一句话简单了解堆和方法区
+
+堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
+
+## 3. 说说并发与并行的区别?
+
+- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
+- **并行:** 单位时间内,多个任务同时执行。
+
+## 4. 为什么要使用多线程呢?
+
+先从总体上来说:
+
+- **从计算机底层来说:** 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
+- **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
+
+再深入到计算机底层来探讨:
+
+- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。
+- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。
+
+## 5. 使用多线程可能带来什么问题?
+
+并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
+
+## 6. 说说线程的生命周期和状态?
+
+Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。
+
+
+
+线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
+
+
+
+由上图可以看出:线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。
+
+> 操作系统隐藏 Java 虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/ "HowToDoInJava"):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/ "Java Thread Life Cycle and Thread States")),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。
+
+
+
+当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的`run()`方法之后将会进入到 **TERMINATED(终止)** 状态。
+
+## 7. 什么是上下文切换?
+
+多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
+
+概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
+
+上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
+
+Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
+
+## 8. 什么是线程死锁?如何避免死锁?
+
+### 8.1. 认识线程死锁
+
+线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
+
+如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
+
+
+
+下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):
+
+```java
+public class DeadLockDemo {
+ private static Object resource1 = new Object();//资源 1
+ private static Object resource2 = new Object();//资源 2
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 1").start();
+
+ new Thread(() -> {
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource1");
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ }
+ }
+ }, "线程 2").start();
+ }
+}
+```
+
+Output
+
+```
+Thread[线程 1,5,main]get resource1
+Thread[线程 2,5,main]get resource2
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 2,5,main]waiting get resource1
+```
+
+线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过`Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
+
+学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
+
+1. 互斥条件:该资源任意一个时刻只由一个线程占用。
+2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
+3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
+4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
+
+### 8.2. 如何避免线程死锁?
+
+我上面说了产生死锁的四个必要条件,为了避免死锁,我们只要破坏产生死锁的四个条件中的其中一个就可以了。现在我们来挨个分析一下:
+
+1. **破坏互斥条件** :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
+2. **破坏请求与保持条件** :一次性申请所有的资源。
+3. **破坏不剥夺条件** :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
+4. **破坏循环等待条件** :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
+
+我们对线程 2 的代码修改成下面这样就不会产生死锁了。
+
+```java
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 2").start();
+```
+
+Output
+
+```
+Thread[线程 1,5,main]get resource1
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 1,5,main]get resource2
+Thread[线程 2,5,main]get resource1
+Thread[线程 2,5,main]waiting get resource2
+Thread[线程 2,5,main]get resource2
+
+Process finished with exit code 0
+```
+
+我们分析一下上面的代码为什么避免了死锁的发生?
+
+线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
+
+## 9. 说说 sleep() 方法和 wait() 方法区别和共同点?
+
+- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。
+- 两者都可以暂停线程的执行。
+- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
+- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
+
+## 10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+
+这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
+
+new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
+
+**总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。**
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号 "公众号")后台回复 **"面试突击"** 即可免费领取!
+
+**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
+
+
diff --git a/docs/java/Multithread/ThreadLocal.md b/docs/java/Multithread/ThreadLocal.md
new file mode 100644
index 00000000..06cdbea5
--- /dev/null
+++ b/docs/java/Multithread/ThreadLocal.md
@@ -0,0 +1,170 @@
+[ThreadLocal造成OOM内存溢出案例演示与原理分析](https://blog.csdn.net/xlgen157387/article/details/78298840)
+
+[深入理解 Java 之 ThreadLocal 工作原理]()
+
+## ThreadLocal
+
+### ThreadLocal简介
+
+通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**
+
+**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。**
+
+再举个简单的例子:
+
+比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。
+
+### ThreadLocal示例
+
+相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。
+
+```java
+import java.text.SimpleDateFormat;
+import java.util.Random;
+
+public class ThreadLocalExample implements Runnable{
+
+ // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
+ private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
+
+ public static void main(String[] args) throws InterruptedException {
+ ThreadLocalExample obj = new ThreadLocalExample();
+ for(int i=0 ; i<10; i++){
+ Thread t = new Thread(obj, ""+i);
+ Thread.sleep(new Random().nextInt(1000));
+ t.start();
+ }
+ }
+
+ @Override
+ public void run() {
+ System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
+ try {
+ Thread.sleep(new Random().nextInt(1000));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ //formatter pattern is changed here by thread, but it won't reflect to other threads
+ formatter.set(new SimpleDateFormat());
+
+ System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
+ }
+
+}
+
+```
+
+Output:
+
+```
+Thread Name= 0 default Formatter = yyyyMMdd HHmm
+Thread Name= 0 formatter = yy-M-d ah:mm
+Thread Name= 1 default Formatter = yyyyMMdd HHmm
+Thread Name= 2 default Formatter = yyyyMMdd HHmm
+Thread Name= 1 formatter = yy-M-d ah:mm
+Thread Name= 3 default Formatter = yyyyMMdd HHmm
+Thread Name= 2 formatter = yy-M-d ah:mm
+Thread Name= 4 default Formatter = yyyyMMdd HHmm
+Thread Name= 3 formatter = yy-M-d ah:mm
+Thread Name= 4 formatter = yy-M-d ah:mm
+Thread Name= 5 default Formatter = yyyyMMdd HHmm
+Thread Name= 5 formatter = yy-M-d ah:mm
+Thread Name= 6 default Formatter = yyyyMMdd HHmm
+Thread Name= 6 formatter = yy-M-d ah:mm
+Thread Name= 7 default Formatter = yyyyMMdd HHmm
+Thread Name= 7 formatter = yy-M-d ah:mm
+Thread Name= 8 default Formatter = yyyyMMdd HHmm
+Thread Name= 9 default Formatter = yyyyMMdd HHmm
+Thread Name= 8 formatter = yy-M-d ah:mm
+Thread Name= 9 formatter = yy-M-d ah:mm
+```
+
+从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。
+
+上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。
+
+```java
+ private static final ThreadLocal formatter = new ThreadLocal(){
+ @Override
+ protected SimpleDateFormat initialValue()
+ {
+ return new SimpleDateFormat("yyyyMMdd HHmm");
+ }
+ };
+```
+
+### ThreadLocal原理
+
+从 `Thread`类源代码入手。
+
+```java
+public class Thread implements Runnable {
+ ......
+//与此线程有关的ThreadLocal值。由ThreadLocal类维护
+ThreadLocal.ThreadLocalMap threadLocals = null;
+
+//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
+ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
+ ......
+}
+```
+
+从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。
+
+`ThreadLocal`类的`set()`方法
+
+```java
+ public void set(T value) {
+ Thread t = Thread.currentThread();
+ ThreadLocalMap map = getMap(t);
+ if (map != null)
+ map.set(this, value);
+ else
+ createMap(t, value);
+ }
+ ThreadLocalMap getMap(Thread t) {
+ return t.threadLocals;
+ }
+```
+
+通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。**
+
+**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。
+
+```java
+public class Thread implements Runnable {
+ ......
+//与此线程有关的ThreadLocal值。由ThreadLocal类维护
+ThreadLocal.ThreadLocalMap threadLocals = null;
+
+//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
+ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
+ ......
+}
+```
+
+`ThreadLocalMap`是`ThreadLocal`的静态内部类。
+
+
+
+### ThreadLocal 内存泄露问题
+
+`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法
+
+```java
+ static class Entry extends WeakReference> {
+ /** The value associated with this ThreadLocal. */
+ Object value;
+
+ Entry(ThreadLocal> k, Object v) {
+ super(k);
+ value = v;
+ }
+ }
+```
+
+**弱引用介绍:**
+
+> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
+>
+> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
diff --git a/docs/java/Multithread/java线程池学习总结.md b/docs/java/Multithread/java线程池学习总结.md
new file mode 100644
index 00000000..329a227e
--- /dev/null
+++ b/docs/java/Multithread/java线程池学习总结.md
@@ -0,0 +1,894 @@
+
+
+
+- [一 使用线程池的好处](#一-使用线程池的好处)
+- [二 Executor 框架](#二-executor-框架)
+ - [2.1 简介](#21-简介)
+ - [2.2 Executor 框架结构(主要由三大部分组成)](#22-executor-框架结构主要由三大部分组成)
+ - [1) 任务(`Runnable` /`Callable`)](#1-任务runnable-callable)
+ - [2) 任务的执行(`Executor`)](#2-任务的执行executor)
+ - [3) 异步计算的结果(`Future`)](#3-异步计算的结果future)
+ - [2.3 Executor 框架的使用示意图](#23-executor-框架的使用示意图)
+- [三 (重要)ThreadPoolExecutor 类简单介绍](#三-重要threadpoolexecutor-类简单介绍)
+ - [3.1 ThreadPoolExecutor 类分析](#31-threadpoolexecutor-类分析)
+ - [3.2 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池](#32-推荐使用-threadpoolexecutor-构造函数创建线程池)
+- [四 (重要)ThreadPoolExecutor 使用示例](#四-重要threadpoolexecutor-使用示例)
+ - [4.1 示例代码:`Runnable`+`ThreadPoolExecutor`](#41-示例代码runnablethreadpoolexecutor)
+ - [4.2 线程池原理分析](#42-线程池原理分析)
+ - [4.3 几个常见的对比](#43-几个常见的对比)
+ - [4.3.1 `Runnable` vs `Callable`](#431-runnable-vs-callable)
+ - [4.3.2 `execute()` vs `submit()`](#432-execute-vs-submit)
+ - [4.3.3 `shutdown()`VS`shutdownNow()`](#433-shutdownvsshutdownnow)
+ - [4.3.2 `isTerminated()` VS `isShutdown()`](#432-isterminated-vs-isshutdown)
+ - [4.4 加餐:`Callable`+`ThreadPoolExecutor`示例代码](#44-加餐callablethreadpoolexecutor示例代码)
+- [五 几种常见的线程池详解](#五-几种常见的线程池详解)
+ - [5.1 FixedThreadPool](#51-fixedthreadpool)
+ - [5.1.1 介绍](#511-介绍)
+ - [5.1.2 执行任务过程介绍](#512-执行任务过程介绍)
+ - [5.1.3 为什么不推荐使用`FixedThreadPool`?](#513-为什么不推荐使用fixedthreadpool)
+ - [5.2 SingleThreadExecutor 详解](#52-singlethreadexecutor-详解)
+ - [5.2.1 介绍](#521-介绍)
+ - [5.2.2 执行任务过程介绍](#522-执行任务过程介绍)
+ - [5.2.3 为什么不推荐使用`SingleThreadExecutor`?](#523-为什么不推荐使用singlethreadexecutor)
+ - [5.3 CachedThreadPool 详解](#53-cachedthreadpool-详解)
+ - [5.3.1 介绍](#531-介绍)
+ - [5.3.2 执行任务过程介绍](#532-执行任务过程介绍)
+ - [5.3.3 为什么不推荐使用`CachedThreadPool`?](#533-为什么不推荐使用cachedthreadpool)
+- [六 ScheduledThreadPoolExecutor 详解](#六-scheduledthreadpoolexecutor-详解)
+ - [6.1 简介](#61-简介)
+ - [6.2 运行机制](#62-运行机制)
+ - [6.3 ScheduledThreadPoolExecutor 执行周期任务的步骤](#63-scheduledthreadpoolexecutor-执行周期任务的步骤)
+- [七 线程池大小确定](#七-线程池大小确定)
+- [八 参考](#八-参考)
+- [九 其他推荐阅读](#九-其他推荐阅读)
+
+
+
+
+## 一 使用线程池的好处
+
+> **池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。**
+
+**线程池**提供了一种限制和管理资源(包括执行一个任务)。 每个**线程池**还维护一些基本统计信息,例如已完成任务的数量。
+
+这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**:
+
+- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
+- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
+- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
+
+## 二 Executor 框架
+
+### 2.1 简介
+
+Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
+
+> 补充:this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
+
+Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
+
+### 2.2 Executor 框架结构(主要由三大部分组成)
+
+#### 1) 任务(`Runnable` /`Callable`)
+
+执行任务需要实现的 **`Runnable` 接口** 或 **`Callable`接口**。**`Runnable` 接口**或 **`Callable` 接口** 实现类都可以被 **`ThreadPoolExecutor`** 或 **`ScheduledThreadPoolExecutor`** 执行。
+
+#### 2) 任务的执行(`Executor`)
+
+如下图所示,包括任务执行机制的核心接口 **`Executor`** ,以及继承自 `Executor` 接口的 **`ExecutorService` 接口。`ThreadPoolExecutor`** 和 **`ScheduledThreadPoolExecutor`** 这两个关键类实现了 **ExecutorService 接口**。
+
+**这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 `ThreadPoolExecutor` 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。**
+
+> **注意:** 通过查看 `ScheduledThreadPoolExecutor` 源代码我们发现 `ScheduledThreadPoolExecutor` 实际上是继承了 `ThreadPoolExecutor` 并实现了 ScheduledExecutorService ,而 `ScheduledExecutorService` 又实现了 `ExecutorService`,正如我们下面给出的类关系图显示的一样。
+
+**`ThreadPoolExecutor` 类描述:**
+
+```java
+//AbstractExecutorService实现了ExecutorService接口
+public class ThreadPoolExecutor extends AbstractExecutorService
+```
+
+**`ScheduledThreadPoolExecutor` 类描述:**
+
+```java
+//ScheduledExecutorService实现了ExecutorService接口
+public class ScheduledThreadPoolExecutor
+ extends ThreadPoolExecutor
+ implements ScheduledExecutorService
+```
+
+
+
+#### 3) 异步计算的结果(`Future`)
+
+**`Future`** 接口以及 `Future` 接口的实现类 **`FutureTask`** 类都可以代表异步计算的结果。
+
+当我们把 **`Runnable`接口** 或 **`Callable` 接口** 的实现类提交给 **`ThreadPoolExecutor`** 或 **`ScheduledThreadPoolExecutor`** 执行。(调用 `submit()` 方法时会返回一个 **`FutureTask`** 对象)
+
+### 2.3 Executor 框架的使用示意图
+
+
+
+1. **主线程首先要创建实现 `Runnable` 或者 `Callable` 接口的任务对象。**
+2. **把创建完成的实现 `Runnable`/`Callable`接口的 对象直接交给 `ExecutorService` 执行**: `ExecutorService.execute(Runnable command)`)或者也可以把 `Runnable` 对象或`Callable` 对象提交给 `ExecutorService` 执行(`ExecutorService.submit(Runnable task)`或 `ExecutorService.submit(Callable task)`)。
+3. **如果执行 `ExecutorService.submit(…)`,`ExecutorService` 将返回一个实现`Future`接口的对象**(我们刚刚也提到过了执行 `execute()`方法和 `submit()`方法的区别,`submit()`会返回一个 `FutureTask 对象)。由于 FutureTask` 实现了 `Runnable`,我们也可以创建 `FutureTask`,然后直接交给 `ExecutorService` 执行。
+4. **最后,主线程可以执行 `FutureTask.get()`方法来等待任务执行完成。主线程也可以执行 `FutureTask.cancel(boolean mayInterruptIfRunning)`来取消此任务的执行。**
+
+## 三 (重要)ThreadPoolExecutor 类简单介绍
+
+**线程池实现类 `ThreadPoolExecutor` 是 `Executor` 框架最核心的类。**
+
+### 3.1 ThreadPoolExecutor 类分析
+
+`ThreadPoolExecutor` 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。
+
+```java
+ /**
+ * 用给定的初始参数创建一个新的ThreadPoolExecutor。
+ */
+ public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
+ int maximumPoolSize,//线程池的最大线程数
+ long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
+ TimeUnit unit,//时间单位
+ BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列
+ ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
+ RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
+ ) {
+ if (corePoolSize < 0 ||
+ maximumPoolSize <= 0 ||
+ maximumPoolSize < corePoolSize ||
+ keepAliveTime < 0)
+ throw new IllegalArgumentException();
+ if (workQueue == null || threadFactory == null || handler == null)
+ throw new NullPointerException();
+ this.corePoolSize = corePoolSize;
+ this.maximumPoolSize = maximumPoolSize;
+ this.workQueue = workQueue;
+ this.keepAliveTime = unit.toNanos(keepAliveTime);
+ this.threadFactory = threadFactory;
+ this.handler = handler;
+ }
+```
+
+**下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!所以,务必拿着小本本记清楚。**
+
+**`ThreadPoolExecutor` 3 个最重要的参数:**
+
+- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。
+- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
+- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。
+
+`ThreadPoolExecutor`其他常见参数:
+
+1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁;
+2. **`unit`** : `keepAliveTime` 参数的时间单位。
+3. **`threadFactory`** :executor 创建新线程的时候会用到。
+4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。
+
+下面这张图可以加深你对线程池中各个参数的相互关系的理解(图片来源:《Java 性能调优实战》):
+
+
+
+**`ThreadPoolExecutor` 饱和策略定义:**
+
+如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略:
+
+- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。
+- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务,也就是直接在调用`execute`方法的线程中运行(`run`)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
+- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。
+- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。
+
+举个例子:
+
+> Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。)
+
+### 3.2 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池
+
+**在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。**
+
+**为什么呢?**
+
+> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。**
+
+**另外《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险**
+
+> Executors 返回线程池对象的弊端如下:
+>
+> - **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
+> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
+
+**方式一:通过`ThreadPoolExecutor`构造函数实现(推荐)**
+
+**方式二:通过 Executor 框架的工具类 Executors 来实现**
+我们可以创建三种类型的 ThreadPoolExecutor:
+
+- **FixedThreadPool**
+- **SingleThreadExecutor**
+- **CachedThreadPool**
+
+对应 Executors 工具类中的方法如图所示:
+
+
+## 四 (重要)ThreadPoolExecutor 使用示例
+
+我们上面讲解了 `Executor`框架以及 `ThreadPoolExecutor` 类,下面让我们实战一下,来通过写一个 `ThreadPoolExecutor` 的小 Demo 来回顾上面的内容。
+
+### 4.1 示例代码:`Runnable`+`ThreadPoolExecutor`
+
+首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。)
+
+`MyRunnable.java`
+
+```java
+import java.util.Date;
+
+/**
+ * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
+ * @author shuang.kou
+ */
+public class MyRunnable implements Runnable {
+
+ private String command;
+
+ public MyRunnable(String s) {
+ this.command = s;
+ }
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
+ processCommand();
+ System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
+ }
+
+ private void processCommand() {
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.command;
+ }
+}
+
+```
+
+编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。
+
+`ThreadPoolExecutorDemo.java`
+
+```java
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPoolExecutorDemo {
+
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAX_POOL_SIZE = 10;
+ private static final int QUEUE_CAPACITY = 100;
+ private static final Long KEEP_ALIVE_TIME = 1L;
+ public static void main(String[] args) {
+
+ //使用阿里巴巴推荐的创建线程池的方式
+ //通过ThreadPoolExecutor构造函数自定义参数创建
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE,
+ MAX_POOL_SIZE,
+ KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(QUEUE_CAPACITY),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ for (int i = 0; i < 10; i++) {
+ //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
+ Runnable worker = new MyRunnable("" + i);
+ //执行Runnable
+ executor.execute(worker);
+ }
+ //终止线程池
+ executor.shutdown();
+ while (!executor.isTerminated()) {
+ }
+ System.out.println("Finished all threads");
+ }
+}
+
+```
+
+可以看到我们上面的代码指定了:
+
+1. `corePoolSize`: 核心线程数为 5。
+2. `maximumPoolSize` :最大线程数 10
+3. `keepAliveTime` : 等待时间为 1L。
+4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。
+5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100;
+6. `handler`:饱和策略为 `CallerRunsPolicy`。
+
+**Output:**
+
+```
+pool-1-thread-3 Start. Time = Sun Apr 12 11:14:37 CST 2020
+pool-1-thread-5 Start. Time = Sun Apr 12 11:14:37 CST 2020
+pool-1-thread-2 Start. Time = Sun Apr 12 11:14:37 CST 2020
+pool-1-thread-1 Start. Time = Sun Apr 12 11:14:37 CST 2020
+pool-1-thread-4 Start. Time = Sun Apr 12 11:14:37 CST 2020
+pool-1-thread-3 End. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-4 End. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-1 End. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-5 End. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-1 Start. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-2 End. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-5 Start. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-4 Start. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-3 Start. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-2 Start. Time = Sun Apr 12 11:14:42 CST 2020
+pool-1-thread-1 End. Time = Sun Apr 12 11:14:47 CST 2020
+pool-1-thread-4 End. Time = Sun Apr 12 11:14:47 CST 2020
+pool-1-thread-5 End. Time = Sun Apr 12 11:14:47 CST 2020
+pool-1-thread-3 End. Time = Sun Apr 12 11:14:47 CST 2020
+pool-1-thread-2 End. Time = Sun Apr 12 11:14:47 CST 2020
+
+```
+
+### 4.2 线程池原理分析
+
+承接 4.1 节,我们通过代码输出结果可以看出:**线程首先会先执行 5 个任务,然后这些任务有任务被执行完的话,就会去拿新的任务执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)
+
+现在,我们就分析上面的输出内容来简单分析一下线程池原理。
+
+**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.1 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:
+
+```java
+ // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
+ private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
+
+ private static int workerCountOf(int c) {
+ return c & CAPACITY;
+ }
+ //任务队列
+ private final BlockingQueue workQueue;
+
+ public void execute(Runnable command) {
+ // 如果任务为null,则抛出异常。
+ if (command == null)
+ throw new NullPointerException();
+ // ctl 中保存的线程池当前的一些状态信息
+ int c = ctl.get();
+
+ // 下面会涉及到 3 步 操作
+ // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
+ // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
+ if (workerCountOf(c) < corePoolSize) {
+ if (addWorker(command, true))
+ return;
+ c = ctl.get();
+ }
+ // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
+ // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去
+ if (isRunning(c) && workQueue.offer(command)) {
+ int recheck = ctl.get();
+ // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
+ if (!isRunning(recheck) && remove(command))
+ reject(command);
+ // 如果当前线程池为空就新创建一个线程并执行。
+ else if (workerCountOf(recheck) == 0)
+ addWorker(null, false);
+ }
+ //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
+ //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
+ else if (!addWorker(command, false))
+ reject(command);
+ }
+```
+
+通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。
+
+
+
+
+
+**`addWorker` 这个方法主要用来创建新的工作线程,如果返回true说明创建和启动工作线程成功,否则的话返回的就是false。**
+
+```java
+ // 全局锁,并发操作必备
+ private final ReentrantLock mainLock = new ReentrantLock();
+ // 跟踪线程池的最大大小,只有在持有全局锁mainLock的前提下才能访问此集合
+ private int largestPoolSize;
+ // 工作线程集合,存放线程池中所有的(活跃的)工作线程,只有在持有全局锁mainLock的前提下才能访问此集合
+ private final HashSet workers = new HashSet<>();
+ //获取线程池状态
+ private static int runStateOf(int c) { return c & ~CAPACITY; }
+ //判断线程池的状态是否为 Running
+ private static boolean isRunning(int c) {
+ return c < SHUTDOWN;
+ }
+
+
+ /**
+ * 添加新的工作线程到线程池
+ * @param firstTask 要执行
+ * @param core参数为true的话表示使用线程池的基本大小,为false使用线程池最大大小
+ * @return 添加成功就返回true否则返回false
+ */
+ private boolean addWorker(Runnable firstTask, boolean core) {
+ retry:
+ for (;;) {
+ //这两句用来获取线程池的状态
+ int c = ctl.get();
+ int rs = runStateOf(c);
+
+ // Check if queue empty only if necessary.
+ if (rs >= SHUTDOWN &&
+ ! (rs == SHUTDOWN &&
+ firstTask == null &&
+ ! workQueue.isEmpty()))
+ return false;
+
+ for (;;) {
+ //获取线程池中线程的数量
+ int wc = workerCountOf(c);
+ // core参数为true的话表明队列也满了,线程池大小变为 maximumPoolSize
+ if (wc >= CAPACITY ||
+ wc >= (core ? corePoolSize : maximumPoolSize))
+ return false;
+ //原子操作将workcount的数量加1
+ if (compareAndIncrementWorkerCount(c))
+ break retry;
+ // 如果线程的状态改变了就再次执行上述操作
+ c = ctl.get();
+ if (runStateOf(c) != rs)
+ continue retry;
+ // else CAS failed due to workerCount change; retry inner loop
+ }
+ }
+ // 标记工作线程是否启动成功
+ boolean workerStarted = false;
+ // 标记工作线程是否创建成功
+ boolean workerAdded = false;
+ Worker w = null;
+ try {
+
+ w = new Worker(firstTask);
+ final Thread t = w.thread;
+ if (t != null) {
+ // 加锁
+ final ReentrantLock mainLock = this.mainLock;
+ mainLock.lock();
+ try {
+ //获取线程池状态
+ int rs = runStateOf(ctl.get());
+ //rs < SHUTDOWN 如果线程池状态依然为RUNNING,并且线程的状态是存活的话,就会将工作线程添加到工作线程集合中
+ //(rs=SHUTDOWN && firstTask == null)如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
+ // firstTask == null证明只新建线程而不执行任务
+ if (rs < SHUTDOWN ||
+ (rs == SHUTDOWN && firstTask == null)) {
+ if (t.isAlive()) // precheck that t is startable
+ throw new IllegalThreadStateException();
+ workers.add(w);
+ //更新当前工作线程的最大容量
+ int s = workers.size();
+ if (s > largestPoolSize)
+ largestPoolSize = s;
+ // 工作线程是否启动成功
+ workerAdded = true;
+ }
+ } finally {
+ // 释放锁
+ mainLock.unlock();
+ }
+ //// 如果成功添加工作线程,则调用Worker内部的线程实例t的Thread#start()方法启动真实的线程实例
+ if (workerAdded) {
+ t.start();
+ /// 标记线程启动成功
+ workerStarted = true;
+ }
+ }
+ } finally {
+ // 线程启动失败,需要从工作线程中移除对应的Worker
+ if (! workerStarted)
+ addWorkerFailed(w);
+ }
+ return workerStarted;
+ }
+```
+
+更多关于线程池源码分析的内容推荐这篇文章:《[JUC线程池ThreadPoolExecutor源码分析](http://www.throwable.club/2019/07/15/java-concurrency-thread-pool-executor/)》
+
+现在,让我们在回到 4.1 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?
+
+没搞懂的话,也没关系,可以看看我的分析:
+
+> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的5个任务中如果有任务被执行完了,线程池就会去拿新的任务执行。
+
+### 4.3 几个常见的对比
+
+#### 4.3.1 `Runnable` vs `Callable`
+
+`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。
+
+工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。
+
+`Runnable.java`
+
+```java
+@FunctionalInterface
+public interface Runnable {
+ /**
+ * 被线程执行,没有返回值也无法抛出异常
+ */
+ public abstract void run();
+}
+```
+
+`Callable.java`
+
+```java
+@FunctionalInterface
+public interface Callable {
+ /**
+ * 计算结果,或在无法这样做时抛出异常。
+ * @return 计算得出的结果
+ * @throws 如果无法计算结果,则抛出异常
+ */
+ V call() throws Exception;
+}
+
+```
+
+#### 4.3.2 `execute()` vs `submit()`
+
+1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**
+2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
+
+我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码:
+
+```java
+ public Future> submit(Runnable task) {
+ if (task == null) throw new NullPointerException();
+ RunnableFuture ftask = newTaskFor(task, null);
+ execute(ftask);
+ return ftask;
+ }
+```
+
+上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。
+
+```java
+ protected RunnableFuture newTaskFor(Runnable runnable, T value) {
+ return new FutureTask(runnable, value);
+ }
+```
+
+我们再来看看`execute()`方法:
+
+```java
+ public void execute(Runnable command) {
+ ...
+ }
+```
+
+#### 4.3.3 `shutdown()`VS`shutdownNow()`
+
+- **`shutdown()`** :关闭线程池,线程池的状态变为 `SHUTDOWN`。线程池不再接受新任务了,但是队列里的任务得执行完毕。
+- **`shutdownNow()`** :关闭线程池,线程的状态变为 `STOP`。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
+
+#### 4.3.2 `isTerminated()` VS `isShutdown()`
+
+- **`isShutDown`** 当调用 `shutdown()` 方法后返回为 true。
+- **`isTerminated`** 当调用 `shutdown()` 方法后,并且所有提交的任务完成后返回为 true
+
+### 4.4 加餐:`Callable`+`ThreadPoolExecutor`示例代码
+
+`MyCallable.java`
+
+```java
+
+import java.util.concurrent.Callable;
+
+public class MyCallable implements Callable {
+ @Override
+ public String call() throws Exception {
+ Thread.sleep(1000);
+ //返回执行当前 Callable 的线程名字
+ return Thread.currentThread().getName();
+ }
+}
+```
+
+`CallableDemo.java`
+
+```java
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class CallableDemo {
+
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAX_POOL_SIZE = 10;
+ private static final int QUEUE_CAPACITY = 100;
+ private static final Long KEEP_ALIVE_TIME = 1L;
+
+ public static void main(String[] args) {
+
+ //使用阿里巴巴推荐的创建线程池的方式
+ //通过ThreadPoolExecutor构造函数自定义参数创建
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE,
+ MAX_POOL_SIZE,
+ KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(QUEUE_CAPACITY),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ List> futureList = new ArrayList<>();
+ Callable callable = new MyCallable();
+ for (int i = 0; i < 10; i++) {
+ //提交任务到线程池
+ Future future = executor.submit(callable);
+ //将返回值 future 添加到 list,我们可以通过 future 获得 执行 Callable 得到的返回值
+ futureList.add(future);
+ }
+ for (Future fut : futureList) {
+ try {
+ System.out.println(new Date() + "::" + fut.get());
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+ //关闭线程池
+ executor.shutdown();
+ }
+}
+```
+
+Output:
+
+```
+Wed Nov 13 13:40:41 CST 2019::pool-1-thread-1
+Wed Nov 13 13:40:42 CST 2019::pool-1-thread-2
+Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3
+Wed Nov 13 13:40:42 CST 2019::pool-1-thread-4
+Wed Nov 13 13:40:42 CST 2019::pool-1-thread-5
+Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3
+Wed Nov 13 13:40:43 CST 2019::pool-1-thread-2
+Wed Nov 13 13:40:43 CST 2019::pool-1-thread-1
+Wed Nov 13 13:40:43 CST 2019::pool-1-thread-4
+Wed Nov 13 13:40:43 CST 2019::pool-1-thread-5
+```
+
+## 五 几种常见的线程池详解
+
+### 5.1 FixedThreadPool
+
+#### 5.1.1 介绍
+
+`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现:
+
+```java
+ /**
+ * 创建一个可重用固定数量线程的线程池
+ */
+ public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
+ return new ThreadPoolExecutor(nThreads, nThreads,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue(),
+ threadFactory);
+ }
+```
+
+另外还有一个 `FixedThreadPool` 的实现方法,和上面的类似,所以这里不多做阐述:
+
+```java
+ public static ExecutorService newFixedThreadPool(int nThreads) {
+ return new ThreadPoolExecutor(nThreads, nThreads,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue());
+ }
+```
+
+**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。**
+
+#### 5.1.2 执行任务过程介绍
+
+`FixedThreadPool` 的 `execute()` 方法运行示意图(该图片来源:《Java 并发编程的艺术》):
+
+
+
+**上图说明:**
+
+1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务;
+2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`;
+3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行;
+
+#### 5.1.3 为什么不推荐使用`FixedThreadPool`?
+
+**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :**
+
+1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize;
+2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。
+3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数;
+4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。
+
+### 5.2 SingleThreadExecutor 详解
+
+#### 5.2.1 介绍
+
+`SingleThreadExecutor` 是只有一个线程的线程池。下面看看**SingleThreadExecutor 的实现:**
+
+```java
+ /**
+ *返回只有一个线程的线程池
+ */
+ public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
+ return new FinalizableDelegatedExecutorService
+ (new ThreadPoolExecutor(1, 1,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue(),
+ threadFactory));
+ }
+```
+
+```java
+ public static ExecutorService newSingleThreadExecutor() {
+ return new FinalizableDelegatedExecutorService
+ (new ThreadPoolExecutor(1, 1,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue()));
+ }
+```
+
+从上面源代码可以看出新创建的 `SingleThreadExecutor` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 1.其他参数和 `FixedThreadPool` 相同。
+
+#### 5.2.2 执行任务过程介绍
+
+**`SingleThreadExecutor` 的运行示意图(该图片来源:《Java 并发编程的艺术》):**
+
+
+**上图说明;**
+
+1. 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务;
+2. 当前线程池中有一个运行的线程后,将任务加入 `LinkedBlockingQueue`
+3. 线程执行完当前的任务后,会在循环中反复从`LinkedBlockingQueue` 中获取任务来执行;
+
+#### 5.2.3 为什么不推荐使用`SingleThreadExecutor`?
+
+`SingleThreadExecutor` 使用无界队列 `LinkedBlockingQueue` 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。`SingleThreadExecutor` 使用无界队列作为线程池的工作队列会对线程池带来的影响与 `FixedThreadPool` 相同。说简单点就是可能会导致 OOM,
+
+### 5.3 CachedThreadPool 详解
+
+#### 5.3.1 介绍
+
+`CachedThreadPool` 是一个会根据需要创建新线程的线程池。下面通过源码来看看 `CachedThreadPool` 的实现:
+
+```java
+ /**
+ * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。
+ */
+ public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
+ return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+ 60L, TimeUnit.SECONDS,
+ new SynchronousQueue(),
+ threadFactory);
+ }
+
+```
+
+```java
+ public static ExecutorService newCachedThreadPool() {
+ return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+ 60L, TimeUnit.SECONDS,
+ new SynchronousQueue());
+ }
+```
+
+`CachedThreadPool` 的`corePoolSize` 被设置为空(0),`maximumPoolSize`被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
+
+#### 5.3.2 执行任务过程介绍
+
+**CachedThreadPool 的 execute()方法的执行示意图(该图片来源:《Java 并发编程的艺术》):**
+
+
+**上图说明:**
+
+1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2;
+2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成;
+
+#### 5.3.3 为什么不推荐使用`CachedThreadPool`?
+
+`CachedThreadPool`允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
+
+## 六 ScheduledThreadPoolExecutor 详解
+
+**`ScheduledThreadPoolExecutor` 主要用来在给定的延迟后运行任务,或者定期执行任务。** 这个在实际项目中基本不会被用到,因为有其他方案选择比如`quartz`。大家只需要简单了解一下它的思想。关于如何在 Spring Boot 中 实现定时任务,可以查看这篇文章[《5 分钟搞懂如何在 Spring Boot 中 Schedule Tasks》](https://github.com/Snailclimb/springboot-guide/blob/master/docs/advanced/SpringBoot-ScheduleTasks.md)。
+
+### 6.1 简介
+
+**`ScheduledThreadPoolExecutor` 使用的任务队列 `DelayQueue` 封装了一个 `PriorityQueue`,`PriorityQueue` 会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(`ScheduledFutureTask` 的 `time` 变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(`ScheduledFutureTask` 的 `squenceNumber` 变量小的先执行)。**
+
+**`ScheduledThreadPoolExecutor` 和 `Timer` 的比较:**
+
+- `Timer` 对系统时钟的变化敏感,`ScheduledThreadPoolExecutor`不是;
+- `Timer` 只有一个执行线程,因此长时间运行的任务可以延迟其他任务。 `ScheduledThreadPoolExecutor` 可以配置任意数量的线程。 此外,如果你想(通过提供 ThreadFactory),你可以完全控制创建的线程;
+- 在`TimerTask` 中抛出的运行时异常会杀死一个线程,从而导致 `Timer` 死机:-( ...即计划任务将不再运行。`ScheduledThreadExecutor` 不仅捕获运行时异常,还允许您在需要时处理它们(通过重写 `afterExecute` 方法`ThreadPoolExecutor`)。抛出异常的任务将被取消,但其他任务将继续运行。
+
+**综上,在 JDK1.5 之后,你没有理由再使用 Timer 进行任务调度了。**
+
+> **备注:** Quartz 是一个由 java 编写的任务调度库,由 OpenSymphony 组织开源出来。在实际项目开发中使用 Quartz 的还是居多,比较推荐使用 Quartz。因为 Quartz 理论上能够同时对上万个任务进行调度,拥有丰富的功能特性,包括任务调度、任务持久化、可集群化、插件等等。
+
+### 6.2 运行机制
+
+
+
+**`ScheduledThreadPoolExecutor` 的执行主要分为两大部分:**
+
+1. 当调用 `ScheduledThreadPoolExecutor` 的 **`scheduleAtFixedRate()`** 方法或者**`scheduleWirhFixedDelay()`** 方法时,会向 `ScheduledThreadPoolExecutor` 的 **`DelayQueue`** 添加一个实现了 **`RunnableScheduledFuture`** 接口的 **`ScheduledFutureTask`** 。
+2. 线程池中的线程从 `DelayQueue` 中获取 `ScheduledFutureTask`,然后执行任务。
+
+**`ScheduledThreadPoolExecutor` 为了实现周期性的执行任务,对 `ThreadPoolExecutor`做了如下修改:**
+
+- 使用 **`DelayQueue`** 作为任务队列;
+- 获取任务的方不同
+- 执行周期任务后,增加了额外的处理
+
+### 6.3 ScheduledThreadPoolExecutor 执行周期任务的步骤
+
+
+
+1. 线程 1 从 `DelayQueue` 中获取已到期的 `ScheduledFutureTask(DelayQueue.take())`。到期任务是指 `ScheduledFutureTask`的 time 大于等于当前系统的时间;
+2. 线程 1 执行这个 `ScheduledFutureTask`;
+3. 线程 1 修改 `ScheduledFutureTask` 的 time 变量为下次将要被执行的时间;
+4. 线程 1 把这个修改 time 之后的 `ScheduledFutureTask` 放回 `DelayQueue` 中(`DelayQueue.add()`)。
+
+## 七 线程池大小确定
+
+**线程池数量的确定一直是困扰着程序员的一个难题,大部分程序员在设定线程池大小的时候就是随心而定。**
+
+很多人甚至可能都会觉得把线程池配置过大一点比较好!我觉得这明显是有问题的。就拿我们生活中非常常见的一例子来说:**并不是人多就能把事情做好,增加了沟通交流成本。你本来一件事情只需要 3 个人做,你硬是拉来了 6 个人,会提升做事效率嘛?我想并不会。** 线程数量过多的影响也是和我们分配多少人做事情一样,对于多线程这个场景来说主要是增加了**上下文切换**成本。不清楚什么是上下文切换的话,可以看我下面的介绍。
+
+> 上下文切换:
+>
+> 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
+>
+> 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
+>
+> Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
+
+**类比于实现世界中的人类通过合作做某件事情,我们可以肯定的一点是线程池大小设置过大或者过小都会有问题,合适的才是最好。**
+
+**如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。**
+
+**但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。**
+
+有一个简单并且适用面比较广的公式:
+
+- **CPU 密集型任务(N+1):** 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
+- **I/O 密集型任务(2N):** 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
+
+**如何判断是 CPU 密集任务还是 IO 密集任务?**
+
+CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
+
+## 八 参考
+
+- 《Java 并发编程的艺术》
+- [Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example](https://www.journaldev.com/2340/java-scheduler-scheduledexecutorservice-scheduledthreadpoolexecutor-example "Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example")
+- [java.util.concurrent.ScheduledThreadPoolExecutor Example](https://examples.javacodegeeks.com/core-java/util/concurrent/scheduledthreadpoolexecutor/java-util-concurrent-scheduledthreadpoolexecutor-example/ "java.util.concurrent.ScheduledThreadPoolExecutor Example")
+- [ThreadPoolExecutor – Java Thread Pool Example](https://www.journaldev.com/1069/threadpoolexecutor-java-thread-pool-example-executorservice "ThreadPoolExecutor – Java Thread Pool Example")
+
+## 九 其他推荐阅读
+
+- [Java 并发(三)线程池原理](https://www.cnblogs.com/warehouse/p/10720781.html "Java并发(三)线程池原理")
+- [如何优雅的使用和理解线程池](https://github.com/crossoverJie/JCSprout/blob/master/MD/ThreadPoolExecutor.md "如何优雅的使用和理解线程池")
diff --git a/docs/java/synchronized.md b/docs/java/Multithread/synchronized.md
similarity index 99%
rename from docs/java/synchronized.md
rename to docs/java/Multithread/synchronized.md
index 0a1f4f2b..3c926654 100644
--- a/docs/java/synchronized.md
+++ b/docs/java/Multithread/synchronized.md
@@ -6,7 +6,7 @@
- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁**
- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。
-- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
+- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
diff --git a/docs/java/Multithread/创建线程的几种方式总结.md b/docs/java/Multithread/创建线程的几种方式总结.md
new file mode 100644
index 00000000..764a63a4
--- /dev/null
+++ b/docs/java/Multithread/创建线程的几种方式总结.md
@@ -0,0 +1,40 @@
+## 面试官:“创建线程有哪几种常见的方式?”
+
+1. 继承 Thread 类
+2. 实现 Runnable 接口
+3. 使用 Executor 框架
+4. 使用 FutureTask
+
+## 最简单的两种方式
+
+### 1.继承 Thread 类
+
+
+
+
+
+### 2.实现 Runnable 接口
+
+## 比较实用的两种方式
+
+### 3.使用 Executor 框架
+
+Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
+
+> 补充:this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
+
+Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
+
+为了能搞懂如何使用 Executor 框架创建
+
+### Executor 框架结构(主要由三大部分组成)
+
+#### 1) 任务(`Runnable` /`Callable`)
+
+执行任务需要实现的 **`Runnable` 接口** 或 **`Callable`接口**。**`Runnable` 接口**或 **`Callable` 接口** 实现类都可以被 **`ThreadPoolExecutor`** 或 **`ScheduledThreadPoolExecutor`** 执行。
+
+#### 2) 任务的执行(`Executor`)
+
+如下图所示,包括任务执行机制的核心接口 **`Executor`** ,以及继承自 `Executor` 接口的 **`ExecutorService` 接口。`ThreadPoolExecutor`** 和 **`ScheduledThreadPoolExecutor`** 这两个关键类实现了 **ExecutorService 接口**。
+
+### 4.使用 FutureTask
\ No newline at end of file
diff --git a/docs/java/Multithread/多线程学习指南.md b/docs/java/Multithread/多线程学习指南.md
new file mode 100644
index 00000000..06381d7c
--- /dev/null
+++ b/docs/java/Multithread/多线程学习指南.md
@@ -0,0 +1,158 @@
+## 前言
+
+这是我的第二篇专门介绍如何去学习某个知识点的文章,在上一篇[《写给 Java 程序员看的算法学习指南!》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486508&idx=1&sn=ce2faafcde166d5412d7166a01fdc1e9&chksm=cea243e7f9d5caf1dbf4d6ccf0438a1731bc0070310bba1ac481d485e4a6756349c20f02a6b1&token=211950660&lang=zh_CN#rd) 的文章中,我推荐了一些关于 **算法学习的书籍以及资源** 。
+
+相比于写技术文章来说,写这种这种类型的文章实际花费的时间可能会稍微少一点。但是,这种学习指南形式的文章,我想对于 Java 初学者甚至是工作几年的 Java 工程师来说应该还是非常有帮助的!
+
+我们都知道多线程应该是大部分 Java 程序员最难啃的一块骨头之一,这部分内容的难度跨度大,难实践,并且市面上的参考资料的质量也层次不齐。
+
+在这篇文章中,我会首先介绍一下 **Java 多线程学习** 中比较重要的一些问题,然后还会推荐一些比较不错的学习资源供大家参考。希望对你们学习多线程相关的知识能有帮助。以下介绍的很多知识点你都可以在这里找到:[https://snailclimb.gitee.io/javaguide/#/?id=并发](https://snailclimb.gitee.io/javaguide/#/?id=并发)
+
+
+
+**另外,我还将本文的内容同步到了 Github 上,点击阅读原文即可直达。如果你觉得有任何需要完善和修改的地方,都可以去 Github 给我提交 Issue 或者 PR(推荐)。**
+
+## 一.Java 多线程知识点总结
+
+### 1.1.多线程基础
+
+1. 什么是线程和进程? 线程与进程的关系,区别及优缺点?
+2. 说说并发与并行的区别?
+3. 为什么要使用多线程呢?
+4. 使用多线程可能带来什么问题?(内存泄漏、死锁、线程不安全等等)
+5. 创建线程有哪几种方式?(a.继承 Thread 类;b.实现 Runnable 接口;c. 使用 Executor 框架;d.使用 FutureTask)
+6. 说说线程的生命周期和状态?
+7. 什么是上下文切换?
+8. 什么是线程死锁?如何避免死锁?
+9. 说说 sleep() 方法和 wait() 方法区别和共同点?
+10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+11. ......
+
+### 1.2.多线程知识进阶
+
+#### volatile 关键字
+
+1. Java 内存模型(**JMM**);
+2. 重排序与 happens-before 原则了解吗?
+3. volatile 关键字的作用;
+4. 说说 synchronized 关键字和 volatile 关键字的区别;
+5. ......
+
+#### ThreadLocal
+
+1. 有啥用(解决了什么问题)?怎么用?
+2. 原理了解吗?
+3. 内存泄露问题了解吗?
+
+#### 线程池
+
+1. 为什么要用线程池?
+2. 你会使用线程池吗?
+3. 如何创建线程池比较好? (推荐使用 `ThreadPoolExecutor` 构造函数创建线程池)
+4. `ThreadPoolExecutor` 类的重要参数了解吗?`ThreadPoolExecutor` 饱和策略了解吗?
+5. 线程池原理了解吗?
+6. 几种常见的线程池了解吗?为什么不推荐使用`FixedThreadPool`?
+7. 如何设置线程池的大小?
+8. ......
+
+#### AQS
+
+1. 简介
+2. 原理
+3. AQS 常用组件。
+ - **Semaphore(信号量)**-允许多个线程同时访问
+ - **CountDownLatch (倒计时器)**-CountDownLatch 允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕。
+ - **CyclicBarrier(循环栅栏)**-CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
+ - **ReentrantLock 和 ReentrantReadWriteLock**
+ - ......
+
+#### 锁
+
+锁的常见分类
+
+1. 可重入锁和非可重入锁
+2. 公平锁与非公平锁
+3. 读写锁和排它锁
+
+**synchronized 关键字**
+
+1. 说一说自己对于 synchronized 关键字的了解;
+2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗;
+3. 讲一下 synchronized 关键字的底层原理;
+4. 说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗;
+5. 谈谈 synchronized 和 ReentrantLock 的区别;
+6. ......
+
+**ReentrantLock 和 ReentrantReadWriteLock**
+
+**ReadWriteLock**
+
+**StampedLock(JDK8)**
+
+#### **Atomic 与 CAS**
+
+**CAS:**
+
+1. 介绍
+2. 原理
+
+**Atomic 原子类:**
+
+1. 介绍一下 Atomic 原子类;
+2. JUC 包中的原子类是哪 4 类?;
+3. 讲讲 AtomicInteger 的使用;
+4. 能不能给我简单介绍一下 AtomicInteger 类的原理。
+5. ......
+
+#### 并发容器
+
+JDK 提供的这些容器大部分在 `java.util.concurrent` 包中。
+
+- **ConcurrentHashMap:** 线程安全的 HashMap
+- **CopyOnWriteArrayList:** 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector.
+- **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
+- **BlockingQueue:** 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
+- **ConcurrentSkipListMap:** 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
+- ......
+
+#### Future 和 CompletableFuture
+
+## 二.书籍推荐
+
+#### 《Java 并发编程之美》
+
+
+
+**我觉得这本书还是非常适合我们用来学习 Java 多线程的。这本书的讲解非常通俗易懂,作者从并发编程基础到实战都是信手拈来。**
+
+另外,这本书的作者加多自身也会经常在网上发布各种技术文章。我觉得这本书也是加多大佬这么多年在多线程领域的沉淀所得的结果吧!他书中的内容基本都是结合代码讲解,非常有说服力!
+
+#### 《实战 Java 高并发程序设计》
+
+
+
+这个是我第二本要推荐的书籍,比较适合作为多线程入门/进阶书籍来看。这本书内容同样是理论结合实战,对于每个知识点的讲解也比较通俗易懂,整体结构也比较清。
+
+#### 《深入浅出 Java 多线程》
+
+
+
+这本书是几位大厂(如阿里)的大佬开源的,Github 地址:[https://github.com/RedSpider1/concurrent](https://github.com/RedSpider1/concurrent)
+
+几位作者为了写好《深入浅出 Java 多线程》这本书阅读了大量的 Java 多线程方面的书籍和博客,然后再加上他们的经验总结、Demo 实例、源码解析,最终才形成了这本书。
+
+这本书的质量也是非常过硬!给作者们点个赞!这本书有统一的排版规则和语言风格、清晰的表达方式和逻辑。并且每篇文章初稿写完后,作者们就会互相审校,合并到主分支时所有成员会再次审校,最后再通篇修订了三遍。
+
+#### 《Java 并发编程的艺术》
+
+
+
+这本书不是很适合作为 Java 多线程入门书籍,需要具备一定的 JVM 基础,有些东西讲的还是挺深入的。另外,就我自己阅读这本书的感觉来说,我觉得这本书的章节规划有点杂乱,但是,具体到某个知识点又很棒!这可能也和这本书由三名作者共同编写完成有关系吧!
+
+**综上:这本书并不是和 Java 多线程入门,你也不需要把这本书的每一章节都看一遍,建议挑选自己想要详细了解的知识点来看。**
+
+## 三.总结
+
+在这篇文章中我主要总结了 Java 多线程方面的知识点,并且推荐了相关的书籍。并发这部分东西实战的话比较难,你可以尝试学会了某个知识点之后然后在自己写过的一些项目上实践。另外,leetcode 有一个练习多线程的类别: [https://leetcode-cn.com/problemset/concurrency](https://leetcode-cn.com/problemset/concurrency) 可以作为参考。
+
+**为了这篇文章的内容更加完善,我还将本文的内容同步到了 Github 上,点击阅读原文即可直达。如果你觉得有任何需要完善和修改的地方,都可以去 Github 给我提交 Issue 或者 PR(推荐)。**
diff --git a/docs/java/Multithread/并发容器总结.md b/docs/java/Multithread/并发容器总结.md
index 22873b3d..ed606342 100644
--- a/docs/java/Multithread/并发容器总结.md
+++ b/docs/java/Multithread/并发容器总结.md
@@ -1,48 +1,46 @@
+点击关注[公众号](#公众号 "公众号")及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。
-- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结)
-- [二 ConcurrentHashMap](#二-concurrenthashmap)
-- [三 CopyOnWriteArrayList](#三-copyonwritearraylist)
- - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介)
- - [3.2 CopyOnWriteArrayList 是如何做到的?](#32-copyonwritearraylist-是如何做到的?)
- - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析)
- - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现)
- - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现)
-- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue)
-- [五 BlockingQueue](#五-blockingqueue)
- - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍)
- - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue)
- - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue)
- - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue)
-- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap)
-- [七 参考](#七-参考)
+- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结 "一 JDK 提供的并发容器总结")
+- [二 ConcurrentHashMap](#二-concurrenthashmap "二 ConcurrentHashMap")
+- [三 CopyOnWriteArrayList](#三-copyonwritearraylist "三 CopyOnWriteArrayList")
+ - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介 "3.1 CopyOnWriteArrayList 简介")
+ - [3.2 CopyOnWriteArrayList 是如何做到的?](#32-copyonwritearraylist-是如何做到的? "3.2 CopyOnWriteArrayList 是如何做到的?")
+ - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析 "3.3 CopyOnWriteArrayList 读取和写入源码简单分析")
+ - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现 "3.3.1 CopyOnWriteArrayList 读取操作的实现")
+ - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现 "3.3.2 CopyOnWriteArrayList 写入操作的实现")
+- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue "四 ConcurrentLinkedQueue")
+- [五 BlockingQueue](#五-blockingqueue "五 BlockingQueue")
+ - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍 "5.1 BlockingQueue 简单介绍")
+ - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue "5.2 ArrayBlockingQueue")
+ - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue "5.3 LinkedBlockingQueue")
+ - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue "5.4 PriorityBlockingQueue")
+- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap "六 ConcurrentSkipListMap")
+- [七 参考](#七-参考 "七 参考")
-## 一 JDK 提供的并发容器总结
+## 一 JDK 提供的并发容器总结
-JDK提供的这些容器大部分在 `java.util.concurrent` 包中。
+JDK 提供的这些容器大部分在 `java.util.concurrent` 包中。
-
-- **ConcurrentHashMap:** 线程安全的HashMap
-- **CopyOnWriteArrayList:** 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.
+- **ConcurrentHashMap:** 线程安全的 HashMap
+- **CopyOnWriteArrayList:** 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector.
- **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
-- **BlockingQueue:** 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
-- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。
+- **BlockingQueue:** 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
+- **ConcurrentSkipListMap:** 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。
## 二 ConcurrentHashMap
我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 `Collections.synchronizedMap()` 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。
-所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
-
-关于 ConcurrentHashMap 相关问题,我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%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.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题:
-
-- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB)
-- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0)
+所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在 ConcurrentHashMap 中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。
+关于 ConcurrentHashMap 相关问题,我在 [Java 集合框架常见面试题](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%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.md "Java集合框架常见面试题") 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题:
+- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%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.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB "ConcurrentHashMap 和 Hashtable 的区别")
+- [ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%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.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0 "ConcurrentHashMap线程安全的具体实现方式/底层具体实现")
## 三 CopyOnWriteArrayList
@@ -54,15 +52,15 @@ extends Object
implements List, RandomAccess, Cloneable, Serializable
```
-在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。
+在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问 List 的内部数据,毕竟读取操作是安全的。
-这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,`CopyOnWriteArrayList` 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。**那它是怎么做的呢?**
+这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK 中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,`CopyOnWriteArrayList` 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。**那它是怎么做的呢?**
### 3.2 CopyOnWriteArrayList 是如何做到的?
- `CopyOnWriteArrayList` 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。
+`CopyOnWriteArrayList` 类的所有可变操作(add,set 等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。
-从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的ArrayList,所谓`CopyOnWrite` 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。
+从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的 ArrayList,所谓`CopyOnWrite` 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。
### 3.3 CopyOnWriteArrayList 读取和写入源码简单分析
@@ -115,19 +113,19 @@ CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁
## 四 ConcurrentLinkedQueue
-Java提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。**
+Java 提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是 ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。**
从名字可以看出,`ConcurrentLinkedQueue`这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。
-ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。
+ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道 ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。
-ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。
+ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。
-## 五 BlockingQueue
+## 五 BlockingQueue
### 5.1 BlockingQueue 简单介绍
-上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
+上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是 BlockingQueue 提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类:
@@ -137,7 +135,7 @@ BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作
### 5.2 ArrayBlockingQueue
-**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类,底层采用**数组**来实现。ArrayBlockingQueue一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
+**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类,底层采用**数组**来实现。ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。
ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:
@@ -147,7 +145,7 @@ private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueu
### 5.3 LinkedBlockingQueue
-**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足FIFO的特性,与ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE。
+**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足 FIFO 的特性,与 ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建 LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于 Integer.MAX_VALUE。
**相关构造方法:**
@@ -188,13 +186,13 @@ PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无
《解读 Java 并发队列 BlockingQueue》
-[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue)
+[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue "https://javadoop.com/post/java-concurrent-queue")
## 六 ConcurrentSkipListMap
-下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster)以及《实战Java高并发程序设计》。
+下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster "《数据结构与算法之美》")以及《实战 Java 高并发程序设计》。
-**为了引出ConcurrentSkipListMap,先带着大家简单理解一下跳表。**
+**为了引出 ConcurrentSkipListMap,先带着大家简单理解一下跳表。**
对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 **O(logn)** 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。
@@ -204,20 +202,28 @@ PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无
最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。
-跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。
+跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素 18。

-查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。
+查找 18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。
从上面很容易看出,**跳表是一种利用空间换时间的算法。**
-使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。
-
-
+使用跳表实现 Map 和使用哈希算法实现 Map 的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是 ConcurrentSkipListMap。
## 七 参考
-- 《实战Java高并发程序设计》
+- 《实战 Java 高并发程序设计》
- https://javadoop.com/post/java-concurrent-queue
- https://juejin.im/post/5aeebd02518825672f19c546
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号 "公众号")后台回复 **"面试突击"** 即可免费领取!
+
+**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/What's New in JDK8/JDK8接口规范-静态、默认方法.md b/docs/java/What's New in JDK8/JDK8接口规范-静态、默认方法.md
deleted file mode 100644
index ee1dd8c2..00000000
--- a/docs/java/What's New in JDK8/JDK8接口规范-静态、默认方法.md
+++ /dev/null
@@ -1,163 +0,0 @@
-JDK8接口规范
-===
-在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。。
----
-## 1.JDK8以前的接口规范
-- JDK8以前接口可以定义的变量和方法
- - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。
- - 所有方法(Method)不论是否显示 的声明为```public abstract```,它实际上都是```public abstract```的。
-```java
-public interface AInterfaceBeforeJDK8 {
- int FIELD = 0;
- void simpleMethod();
-}
-```
-以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。
-```java
-{
- public static final int FIELD;
- descriptor: I
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
- ConstantValue: int 0
-
- public abstract void simpleMethod();
- descriptor: ()V
- flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
-}
-```
-## 2.JDK8之后的接口规范
-- JDK8之后接口可以定义的变量和方法
- - 变量(Field)仍然必须是 ```java public static final```的
- - 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。
-从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
- 下面是一个JDK8之后的接口例子
-```java
-public interface AInterfaceInJDK8 {
- int simpleFiled = 0;
- static int staticField = 1;
-
- public static void main(String[] args) {
- }
- static void staticMethod(){}
-
- default void defaultMethod(){}
-
- void simpleMethod() throws IOException;
-
-}
-```
-进行反编译(去除了一些没用信息)
-```java
-{
- public static final int simpleFiled;
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
- public static final int staticField;
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
- public static void main(java.lang.String[]);
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-
- public static void staticMethod();
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-
- public void defaultMethod();
- flags: (0x0001) ACC_PUBLIC
-
- public abstract void simpleMethod() throws java.io.IOException;
- flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
- Exceptions:
- throws java.io.IOException
-}
-```
-可以看到 default关键字修饰的方法是像实例方法一样定义的,所以我们来定义一个只有default的方法并且实现一下试一试。
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-
-public class DefaultMethod implements Default {
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
- //! DefaultMethod.defaultMethod();
- }
-}
-```
-可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以覆盖该方法。
-这有点像子类继承父类实例方法。
-
-接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
-```java
-interface Static {
- static int staticMethod() {
- return 4396;
- }
-}
- ... main(String...args)
- //!compile error: Static method may be invoked on containing interface class only
- //!aInstanceOfStatic.staticMethod();
- ...
-```
-另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的,
-那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-interface Default2 extends Default {
- @Override
- default int defaultMethod() {
- return 9527;
- }
-}
-public class DefaultMethod implements Default,Default2 {
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- }
-}
-
-输出 : 9527
-```
-出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
-
-那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它
-还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-
-interface Default2 {
- default int defaultMethod() {
- return 9527;
- }
-}
-//如果不重写
-//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
-public class DefaultMethod implements Default,Default2 {
-@Override
- public int defaultMethod() {
- System.out.println(Default.super.defaultMethod());
- System.out.println(Default2.super.defaultMethod());
- return 996;
- }
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- }
-}
-
-运行输出 :
-4396
-9527
-996
-```
diff --git a/docs/java/What's New in JDK8/Java8Tutorial.md b/docs/java/What's New in JDK8/Java8Tutorial.md
index 4dd12ecf..01e71f5e 100644
--- a/docs/java/What's New in JDK8/Java8Tutorial.md
+++ b/docs/java/What's New in JDK8/Java8Tutorial.md
@@ -1,3 +1,5 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
随着 Java 8 的普及度越来越高,很多人都提到面试中关于Java 8 也是非常常问的知识点。应各位要求和需要,我打算对这部分知识做一个总结。本来准备自己总结的,后面看到Github 上有一个相关的仓库,地址:
[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的,我对其进行了翻译并添加和修改了部分内容,下面是正文了。
@@ -30,14 +32,14 @@
- [Sequential Sort\(串行排序\)](#sequential-sort串行排序)
- [Parallel Sort\(并行排序\)](#parallel-sort并行排序)
- [Maps](#maps)
- - [Data API\(日期相关API\)](#data-api日期相关api)
+ - [Date API\(日期相关API\)](#date-api日期相关api)
- [Clock](#clock)
- [Timezones\(时区\)](#timezones时区)
- [LocalTime\(本地时间\)](#localtime本地时间)
- [LocalDate\(本地日期\)](#localdate本地日期)
- [LocalDateTime\(本地日期时间\)](#localdatetime本地日期时间)
- [Annotations\(注解\)](#annotations注解)
- - [Whete to go from here?](#whete-to-go-from-here)
+ - [Where to go from here?](#where-to-go-from-here)
@@ -71,7 +73,7 @@ Formula 接口中除了抽象方法计算接口公式还定义了默认方法 `s
public class Main {
public static void main(String[] args) {
- // TODO 通过匿名内部类方式访问接口
+ // 通过匿名内部类方式访问接口
Formula formula = new Formula() {
@Override
public double calculate(int a) {
@@ -442,15 +444,15 @@ optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
首先看看Stream是怎么用,首先创建实例代码的用到的数据List:
```java
-List stringCollection = new ArrayList<>();
-stringCollection.add("ddd2");
-stringCollection.add("aaa2");
-stringCollection.add("bbb1");
-stringCollection.add("aaa1");
-stringCollection.add("bbb3");
-stringCollection.add("ccc");
-stringCollection.add("bbb2");
-stringCollection.add("ddd1");
+List stringList = new ArrayList<>();
+stringList.add("ddd2");
+stringList.add("aaa2");
+stringList.add("bbb1");
+stringList.add("aaa1");
+stringList.add("bbb3");
+stringList.add("ccc");
+stringList.add("bbb2");
+stringList.add("ddd1");
```
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:
@@ -492,7 +494,7 @@ forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
-下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
+下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
```java
// 测试 Map 操作
@@ -705,7 +707,7 @@ map.get(9); // val9concat
Merge 做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。
-## Data API(日期相关API)
+## Date API(日期相关API)
Java 8在 `java.time` 包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似,但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。
@@ -916,9 +918,16 @@ System.out.println(hints2.length); // 2
@interface MyAnnotation {}
```
-
-
-## Whete to go from here?
+## Where to go from here?
关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如`Arrays.parallelSort`, `StampedLock`和`CompletableFuture`等等。
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/What's New in JDK8/Java8foreach指南.md b/docs/java/What's New in JDK8/Java8foreach指南.md
new file mode 100644
index 00000000..48cda3ab
--- /dev/null
+++ b/docs/java/What's New in JDK8/Java8foreach指南.md
@@ -0,0 +1,139 @@
+> 本文由 JavaGuide 翻译,原文地址:https://www.baeldung.com/foreach-java
+
+## 1 概述
+
+在Java 8中引入的*forEach*循环为程序员提供了一种新的,简洁而有趣的迭代集合的方式。
+
+在本文中,我们将看到如何将*forEach*与集合*一起*使用,它采用何种参数以及此循环与增强的*for*循环的不同之处。
+
+## 2 基础知识
+
+```Java
+public interface Collection extends Iterable
+```
+
+Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:
+
+```java
+void forEach(Consumer super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
+```
+
+使用*forEach*,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他*迭代器一样。*
+
+例如,迭代和打印字符串集合*的*for循环版本:
+
+```java
+for (String name : names) {
+ System.out.println(name);
+}
+```
+
+我们可以使用*forEach*写这个 :
+
+```java
+names.forEach(name -> {
+ System.out.println(name);
+});
+```
+
+## 3.使用forEach方法
+
+### 3.1 匿名类
+
+我们使用 *forEach*迭代集合并对每个元素执行特定操作。**要执行的操作包含在实现Consumer接口的类中,并作为参数传递给forEach 。**
+
+所述*消费者*接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。
+
+Consumer 接口定义如下:
+
+```java
+@FunctionalInterface
+public interface Consumer {
+ void accept(T t);
+}
+```
+任何实现,例如,只是打印字符串的消费者:
+
+```java
+Consumer printConsumer = new Consumer() {
+ public void accept(String name) {
+ System.out.println(name);
+ };
+};
+```
+
+可以作为参数传递给*forEach*:
+
+```java
+names.forEach(printConsumer);
+```
+
+但这不是通过消费者和使用*forEach* API 创建操作的唯一方法。让我们看看我们将使用*forEach*方法的另外2种最流行的方式:
+
+### 3.2 Lambda表达式
+
+Java 8功能接口的主要优点是我们可以使用Lambda表达式来实例化它们,并避免使用庞大的匿名类实现。
+
+由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:
+
+```java
+(argument) -> { body }
+name -> System.out.println(name)
+names.forEach(name -> System.out.println(name));
+```
+
+### 3.3 方法参考
+
+我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:
+
+```java
+names.forEach(System.out::println);
+```
+
+## 4.forEach在集合中的使用
+
+### 4.1.迭代集合
+
+**任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。**
+
+因此,正如我们已经看到的,迭代列表的元素:
+
+```java
+List names = Arrays.asList("Larry", "Steve", "James");
+
+names.forEach(System.out::println);
+```
+
+同样对于一组:
+
+```java
+Set uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
+
+uniqueNames.forEach(System.out::println);
+```
+
+或者让我们说一个*队列*也是一个*集合*:
+
+```java
+Queue namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
+
+namesQueue.forEach(System.out::println);
+```
+
+### 4.2.迭代Map - 使用Map的forEach
+
+Map没有实现Iterable接口,但它**提供了自己的forEach 变体,它接受BiConsumer**。*
+
+```java
+Map namesMap = new HashMap<>();
+namesMap.put(1, "Larry");
+namesMap.put(2, "Steve");
+namesMap.put(3, "James");
+namesMap.forEach((key, value) -> System.out.println(key + " " + value));
+```
+
+### 4.3.迭代一个Map - 通过迭代entrySet
+
+```java
+namesMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));
+```
\ No newline at end of file
diff --git a/docs/java/What's New in JDK8/Java8教程推荐.md b/docs/java/What's New in JDK8/Java8教程推荐.md
index 43e4539c..7de58352 100644
--- a/docs/java/What's New in JDK8/Java8教程推荐.md
+++ b/docs/java/What's New in JDK8/Java8教程推荐.md
@@ -1,5 +1,3 @@
-
-
### 书籍
- **《Java8 In Action》**
diff --git a/docs/java/What's New in JDK8/Lambda表达式.md b/docs/java/What's New in JDK8/Lambda表达式.md
deleted file mode 100644
index 359c4714..00000000
--- a/docs/java/What's New in JDK8/Lambda表达式.md
+++ /dev/null
@@ -1,235 +0,0 @@
-JDK8--Lambda表达式
-===
-## 1.什么是Lambda表达式
-**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递**
-
-## 2.Lambda表达式语法
-```(参数列表) -> {执行代码块}```
- 参数列表可以为空```()->{}```
- 可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值.
- 在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
- (lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
-
-## 3.函数式接口
-在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
-**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
-当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子:
-
-```java
- Thread thread = new Thread(() -> {
- System.out.println("This is JDK8's Lambda!");
- });
-```
-这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的?
-```java
-public Thread(Runnable target) {...}
-
-/**
- * Runnable Interface
- */
-@FunctionalInterface
-public interface Runnable {
- public abstract void run();
-}
-```
-到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
-```java
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("Anonymous class");
- }
- });
-```
-也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都
-由编译器帮我们推断,因此可以减少很多代码量)。
- Lambda也可以这样用 :
-```java
- Runnable runnable = () -> {...};
-```
-其实这和上面的用法没有什么本质上的区别。
- 至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范,
-目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
-因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
-
-除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口
-比如(当然不止这些):
-- ```java.util.concurrent.Callable```
-- ```java.util.Comparator```
-- ```java.io.FileFilter```
- **关于JDK中的预定义的函数式接口**
-
-- JDK在```java.util.function```下预定义了很多函数式接口
- - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。
- - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。
- - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。
- - ```Supplier {T get();} 提供者(工厂)``` 返回一个T对象。
- - 其他的跟上面的相似,大家可以看一下function包下的具体接口。
-## 4.变量作用域
-```java
-public class VaraibleHide {
- @FunctionalInterface
- interface IInner {
- void printInt(int x);
- }
- public static void main(String[] args) {
- int x = 20;
- IInner inner = new IInner() {
- int x = 10;
- @Override
- public void printInt(int x) {
- System.out.println(x);
- }
- };
- inner.printInt(30);
-
- inner = (s) -> {
- //Variable used in lambda expression should be final or effectively final
- //!int x = 10;
- //!x= 50; error
- System.out.print(x);
- };
- inner.printInt(30);
- }
-}
-输出 :
-30
-20
-```
-对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。
- lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,
-同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该
-不难理解。
-## 5.方法引用
-**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。**
- 下面的例子会打印list中的每个元素:
-```java
-List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(System.out::println);
-```
-其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}```
- 我们看一下List#forEach方法 ```default void forEach(Consumer super T> action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口
-```java
-@FunctionalInterface
-public interface Consumer {
- void accept(T t);
-```
-大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
- 我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
-```java
-public class MethodReference {
- public static void main(String[] args) {
- List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(MethodReference::myPrint);
- }
-
- static void myPrint(int i) {
- System.out.print(i + ", ");
- }
-}
-
-输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-```
-可以看到,我们自己定义的方法也可以当做方法引用。
- 到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
-- 方法引用
- - 类名::静态方法名
- - 类名::实例方法名
- - 类名::new (构造方法引用)
- - 实例名::实例方法名
-可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
-方法引用的使用方法。
-**类名::实例方法名**
-先来看一段代码
-```java
- String[] strings = new String[10];
- Arrays.sort(strings, String::compareToIgnoreCase);
-```
-**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
-我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator super T> c)`,
-可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下
-`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
-的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
- 是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是
-instance.method(x)中的instance,
-它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
-参数。我们再举一个自己实现的例子:
-```java
-public class MethodReference {
- static Random random = new Random(47);
- public static void main(String[] args) {
- MethodReference[] methodReferences = new MethodReference[10];
- Arrays.sort(methodReferences, MethodReference::myCompare);
- }
- int myCompare(MethodReference o) {
- return random.nextInt(2) - 1;
- }
-}
-```
-上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。
- **构造器引用**
-构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
-```java
- //compile error : String is not a functional interface
- String str = String::new;
-```
-下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
-```java
- interface IFunctional {
- T func();
-}
-
-public class ConstructorReference {
-
- public ConstructorReference() {
- }
-
- public static void main(String[] args) {
- Supplier supplier0 = () -> new ConstructorReference();
- Supplier supplier1 = ConstructorReference::new;
- IFunctional functional = () -> new ConstructorReference();
- IFunctional functional1 = ConstructorReference::new;
- }
-}
-```
-下面是一个JDK官方的例子
-```java
- public static , DEST extends Collection>
- DEST transferElements(
- SOURCE sourceCollection,
- Supplier collectionFactory) {
-
- DEST result = collectionFactory.get();
- for (T t : sourceCollection) {
- result.add(t);
- }
- return result;
- }
-
- ...
-
- Set rosterSet = transferElements(
- roster, HashSet::new);
-```
-
-**实例::实例方法**
-
-其实开始那个例子就是一个实例::实例方法的引用
-```java
-List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(System.out::println);
-```
-其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。
-## 总结
-Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量,
-但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码,
-所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。
diff --git a/docs/java/What's New in JDK8/README.md b/docs/java/What's New in JDK8/README.md
deleted file mode 100644
index fa71e907..00000000
--- a/docs/java/What's New in JDK8/README.md
+++ /dev/null
@@ -1,556 +0,0 @@
-JDK8新特性总结
-======
-总结了部分JDK8新特性,另外一些新特性可以通过Oracle的官方文档查看,毕竟是官方文档,各种新特性都会介绍,有兴趣的可以去看。
-[Oracle官方文档:What's New in JDK8](https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html)
------
-- [Java语言特性](#JavaProgrammingLanguage)
- - [Lambda表达式是一个新的语言特性,已经在JDK8中加入。它是一个可以传递的代码块,你也可以把它们当做方法参数。
- Lambda表达式允许您更紧凑地创建单虚方法接口(称为功能接口)的实例。](#LambdaExpressions)
-
- - [方法引用为已经存在的具名方法提供易于阅读的Lambda表达式](#MethodReferences)
-
- - [默认方法允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。](#DefaultMethods)
-
- - [改进的类型推断。](#ImprovedTypeInference)
-
- - [方法参数反射(通过反射获得方法参数信息)](#MethodParameterReflection)
-
-- [流(stream)](#stream)
- - [新java.util.stream包中的类提供Stream API以支持对元素流的功能样式操作。流(stream)和I/O里的流不是同一个概念
- ,使用stream API可以更方便的操作集合。]()
-
-- [国际化]()
- - 待办
-- 待办
-___
-
-
-
-
-
-
-
-## Lambda表达式
-### 1.什么是Lambda表达式
-**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递**
-
-### 2.Lambda表达式语法
-```(参数列表) -> {执行代码块}```
- 参数列表可以为空```()->{}```
- 可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值.
- 在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}```
- (lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言)
-
-### 3.函数式接口
-在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
-**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
-当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子:
-
-```java
- Thread thread = new Thread(() -> {
- System.out.println("This is JDK8's Lambda!");
- });
-```
-这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的?
-```java
-public Thread(Runnable target) {...}
-
-/**
- * Runnable Interface
- */
-@FunctionalInterface
-public interface Runnable {
- public abstract void run();
-}
-```
-到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于
-```java
- Thread thread = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("Anonymous class");
- }
- });
-```
-也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都
-由编译器帮我们推断,因此可以减少很多代码量)。
- Lambda也可以这样用 :
-```java
- Runnable runnable = () -> {...};
-```
-其实这和上面的用法没有什么本质上的区别。
- 至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范,
-目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了,
-因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]()
-
-除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口
-比如(当然不止这些):
-- ```java.util.concurrent.Callable```
-- ```java.util.Comparator```
-- ```java.io.FileFilter```
- **关于JDK中的预定义的函数式接口**
-
-- JDK在```java.util.function```下预定义了很多函数式接口
- - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。
- - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。
- - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。
- - ```Supplier {T get();} 提供者(工厂)``` 返回一个T对象。
- - 其他的跟上面的相似,大家可以看一下function包下的具体接口。
-### 4.变量作用域
-```java
-public class VaraibleHide {
- @FunctionalInterface
- interface IInner {
- void printInt(int x);
- }
- public static void main(String[] args) {
- int x = 20;
- IInner inner = new IInner() {
- int x = 10;
- @Override
- public void printInt(int x) {
- System.out.println(x);
- }
- };
- inner.printInt(30);
-
- inner = (s) -> {
- //Variable used in lambda expression should be final or effectively final
- //!int x = 10;
- //!x= 50; error
- System.out.print(x);
- };
- inner.printInt(30);
- }
-}
-输出 :
-30
-20
-```
-对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda
-的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。
- lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。
-
-### 5.方法引用
-**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。**
- 下面的例子会打印list中的每个元素:
-```java
-List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(System.out::println);
-```
-其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}```
- 我们看一下List#forEach方法 ```default void forEach(Consumer super T> action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口
-```java
-@FunctionalInterface
-public interface Consumer {
- void accept(T t);
-```
-大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。
- 我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用
-```java
-public class MethodReference {
- public static void main(String[] args) {
- List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(MethodReference::myPrint);
- }
-
- static void myPrint(int i) {
- System.out.print(i + ", ");
- }
-}
-
-输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
-```
-可以看到,我们自己定义的方法也可以当做方法引用。
- 到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。
-- 方法引用
- - 类名::静态方法名
- - 类名::实例方法名
- - 类名::new (构造方法引用)
- - 实例名::实例方法名
-可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种
-方法引用的使用方法。
-**类名::实例方法名**
-先来看一段代码
-```java
- String[] strings = new String[10];
- Arrays.sort(strings, String::compareToIgnoreCase);
-```
-**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
-我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator super T> c)`,
-可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下
-`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它
-的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样?
- 是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是
-instance.method(x)中的instance,
-它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的
-参数。我们再举一个自己实现的例子:
-```java
-public class MethodReference {
- static Random random = new Random(47);
- public static void main(String[] args) {
- MethodReference[] methodReferences = new MethodReference[10];
- Arrays.sort(methodReferences, MethodReference::myCompare);
- }
- int myCompare(MethodReference o) {
- return random.nextInt(2) - 1;
- }
-}
-```
-上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。
- **构造器引用**
-构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口
-```java
- //compile error : String is not a functional interface
- String str = String::new;
-```
-下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。
-```java
- interface IFunctional {
- T func();
-}
-
-public class ConstructorReference {
-
- public ConstructorReference() {
- }
-
- public static void main(String[] args) {
- Supplier supplier0 = () -> new ConstructorReference();
- Supplier supplier1 = ConstructorReference::new;
- IFunctional functional = () -> new ConstructorReference();
- IFunctional functional1 = ConstructorReference::new;
- }
-}
-```
-下面是一个JDK官方的例子
-```java
- public static , DEST extends Collection>
- DEST transferElements(
- SOURCE sourceCollection,
- Supplier collectionFactory) {
-
- DEST result = collectionFactory.get();
- for (T t : sourceCollection) {
- result.add(t);
- }
- return result;
- }
-
- ...
-
- Set rosterSet = transferElements(
- roster, HashSet::new);
-```
-
-**实例::实例方法**
-
-其实开始那个例子就是一个实例::实例方法的引用
-```java
-List list = new ArrayList<>();
- for (int i = 0; i < 10; ++i) {
- list.add(i);
- }
- list.forEach(System.out::println);
-```
-其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。
-### 总结
-Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量,
-但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码,
-所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。
-
-
-___
-
-
-## JDK8接口规范
-### 在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法,否则会报错。为了保持兼容性而做出妥协的特性还有泛型,泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。
-### 1.JDK8以前的接口规范
-- JDK8以前接口可以定义的变量和方法
- - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。
- - 所有方法(Method)不论是否显示 的声明为```public abstract```,它实际上都是```public abstract```的。
-```java
-public interface AInterfaceBeforeJDK8 {
- int FIELD = 0;
- void simpleMethod();
-}
-```
-以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。
-```java
-{
- public static final int FIELD;
- descriptor: I
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
- ConstantValue: int 0
-
- public abstract void simpleMethod();
- descriptor: ()V
- flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
-}
-```
-### 2.JDK8之后的接口规范
-- JDK8之后接口可以定义的变量和方法
- - 变量(Field)仍然必须是 ```java public static final```的
- - 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。
-从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。
- 下面是一个JDK8之后的接口例子
-```java
-public interface AInterfaceInJDK8 {
- int simpleFiled = 0;
- static int staticField = 1;
-
- public static void main(String[] args) {
- }
- static void staticMethod(){}
-
- default void defaultMethod(){}
-
- void simpleMethod() throws IOException;
-
-}
-```
-进行反编译(去除了一些没用信息)
-```java
-{
- public static final int simpleFiled;
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
- public static final int staticField;
- flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
- public static void main(java.lang.String[]);
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-
- public static void staticMethod();
- flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-
- public void defaultMethod();
- flags: (0x0001) ACC_PUBLIC
-
- public abstract void simpleMethod() throws java.io.IOException;
- flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
- Exceptions:
- throws java.io.IOException
-}
-```
-可以看到 default关键字修饰的方法是像实例方法(就是普通类中定义的普通方法)一样定义的,所以我们来定义一个只有default方法的接口并且实现一下这个接口试一
-试。
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-
-public class DefaultMethod implements Default {
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context
- //! DefaultMethod.defaultMethod();
- }
-}
-```
-可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以选择覆盖该方法。
-这有点像子类继承父类实例方法。
-
-接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用**
-```java
-interface Static {
- static int staticMethod() {
- return 4396;
- }
-}
- ... main(String...args)
- //!compile error: Static method may be invoked on containing interface class only
- //!aInstanceOfStatic.staticMethod();
- ...
-```
-另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的,
-那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-interface Default2 extends Default {
- @Override
- default int defaultMethod() {
- return 9527;
- }
-}
-public class DefaultMethod implements Default,Default2 {
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- }
-}
-
-输出 : 9527
-```
-出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。
-
-那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。
-```java
-interface Default {
- default int defaultMethod() {
- return 4396;
- }
-}
-
-interface Default2 {
- default int defaultMethod() {
- return 9527;
- }
-}
-//如果不重写
-//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2
-public class DefaultMethod implements Default,Default2 {
-@Override
- public int defaultMethod() {
- System.out.println(Default.super.defaultMethod());
- System.out.println(Default2.super.defaultMethod());
- return 996;
- }
- public static void main(String[] args) {
- DefaultMethod defaultMethod = new DefaultMethod();
- System.out.println(defaultMethod.defaultMethod());
- }
-}
-
-运行输出 :
-4396
-9527
-996
-```
-
-
-___
-
-
-## 改进的类型推断
-### 1.什么是类型推断
-类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。
-看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
-```java
- Map map = new Map();
-```
-而我们平常写的都是这样的:
-```java
- Map map = new Map<>();
-```
-这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题
-(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。
-(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽)
-
-在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译
-错误
-```java
- List stringList = new ArrayList<>();
- stringList.add("A");
- //error : addAll(java.util.Collection extends java.lang.String>)in List cannot be applied to (java.util.List)
- stringList.addAll(Arrays.asList());
-```
-但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码
-会更少。加强的类型推断还有一个就是用于Lambda表达式了。
-
-大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息,
-相信随着Java的进化,这些便利的功能会越来越强大。
-
-
-____
-
-
-## 通过反射获得方法的参数信息
-JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里
-也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外
-的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如
-LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上
--parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该
-参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
-```java
-public class ByteCodeParameters {
- public String simpleMethod(String canUGetMyName, Object yesICan) {
- return "9527";
- }
-}
-```
-先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
-```java
- //只截取了部分信息
- public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
- descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=3, args_size=3
- 0: ldc #2 // String 9527
- 2: areturn
- LineNumberTable:
- line 5: 0
- //这个方法的描述到这里就结束了
-```
-接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
-```java
- public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
- descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=3, args_size=3
- 0: ldc #2 // String 9527
- 2: areturn
- LineNumberTable:
- line 8: 0
- MethodParameters:
- Name Flags
- canUGetMyName
- yesICan
-```
-可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。
- 下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 :
-```java
-public class ByteCodeParameters {
- public String simpleMethod(String canUGetMyName, Object yesICan) {
- return "9527";
- }
-
- public static void main(String[] args) throws NoSuchMethodException {
- Class> clazz = ByteCodeParameters.class;
- Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
- Parameter[] parameters = simple.getParameters();
- for (Parameter p : parameters) {
- System.out.println(p.getName());
- }
- }
-}
-输出 :
-arg0
-arg1
-```
-???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在
-Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
-己编译一个.class文件放在IDEA的out下,然后再来运行 :
-```java
-输出 :
-canUGetMyName
-yesICan
-```
-这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
-(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
-
-## 总结与补充
-在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework
-里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。
-
-____
-
-
-
-
-___
diff --git a/docs/java/What's New in JDK8/Stream.md b/docs/java/What's New in JDK8/Stream.md
deleted file mode 100644
index de7c86e3..00000000
--- a/docs/java/What's New in JDK8/Stream.md
+++ /dev/null
@@ -1,75 +0,0 @@
-Stream API 旨在让编码更高效率、干净、简洁。
-
-### 从迭代器到Stream操作
-
-当使用 `Stream` 时,我们一般会通过三个阶段建立一个流水线:
-
-1. 创建一个 `Stream`;
-2. 进行一个或多个中间操作;
-3. 使用终止操作产生一个结果,`Stream` 就不会再被使用了。
-
-**案例1:统计 List 中的单词长度大于6的个数**
-
-```java
-/**
-* 案例1:统计 List 中的单词长度大于6的个数
-*/
-ArrayList wordsList = new ArrayList();
-wordsList.add("Charles");
-wordsList.add("Vincent");
-wordsList.add("William");
-wordsList.add("Joseph");
-wordsList.add("Henry");
-wordsList.add("Bill");
-wordsList.add("Joan");
-wordsList.add("Linda");
-int count = 0;
-```
-Java8之前我们通常用迭代方法来完成上面的需求:
-
-```java
-//迭代(Java8之前的常用方法)
-//迭代不好的地方:1. 代码多;2 很难被并行运算。
-for (String word : wordsList) {
- if (word.length() > 6) {
- count++;
- }
-}
-System.out.println(count);//3
-```
-Java8之前我们使用 `Stream` 一行代码就能解决了,而且可以瞬间转换为并行执行的效果:
-
-```java
-//Stream
-//将stream()改为parallelStream()就可以瞬间将代码编程并行执行的效果
-long count2=wordsList.stream()
- .filter(w->w.length()>6)
- .count();
-long count3=wordsList.parallelStream()
- .filter(w->w.length()>6)
- .count();
-System.out.println(count2);
-System.out.println(count3);
-```
-
-### `distinct()`
-
-去除 List 中重复的 String
-
-```java
-List list = list.stream()
- .distinct()
- .collect(Collectors.toList());
-```
-
-### `map`
-
-map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
-
-```java
-List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
-// 获取 List 中每个元素对应的平方数并去重
-List squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
-System.out.println(squaresList.toString());//[9, 4, 49, 25]
-```
-
diff --git a/docs/java/What's New in JDK8/改进的类型推断.md b/docs/java/What's New in JDK8/改进的类型推断.md
deleted file mode 100644
index b5cff7bb..00000000
--- a/docs/java/What's New in JDK8/改进的类型推断.md
+++ /dev/null
@@ -1,30 +0,0 @@
-## 改进的类型推断
-### 1.什么是类型推断
-类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。
-看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的:
-```java
- Map map = new Map();
-```
-而我们平常写的都是这样的:
-```java
- Map map = new Map<>();
-```
-这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题
-(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。
-(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽)
-
-在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译
-错误
-```java
- List stringList = new ArrayList<>();
- stringList.add("A");
- //error : addAll(java.util.Collection extends java.lang.String>)in List cannot be applied to (java.util.List)
- stringList.addAll(Arrays.asList());
-```
-但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码
-会更少。加强的类型推断还有一个就是用于Lambda表达式了。
-
-大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息,
-相信随着Java的进化,这些便利的功能会越来越强大。
-
-
diff --git a/docs/java/What's New in JDK8/通过反射获得方法的参数信息.md b/docs/java/What's New in JDK8/通过反射获得方法的参数信息.md
deleted file mode 100644
index a1d91c4b..00000000
--- a/docs/java/What's New in JDK8/通过反射获得方法的参数信息.md
+++ /dev/null
@@ -1,79 +0,0 @@
-## 通过反射获得方法的参数信息
-JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里
-也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外
-的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如
-LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上
--parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该
-参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
-```java
-public class ByteCodeParameters {
- public String simpleMethod(String canUGetMyName, Object yesICan) {
- return "9527";
- }
-}
-```
-先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
-```java
- //只截取了部分信息
- public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
- descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=3, args_size=3
- 0: ldc #2 // String 9527
- 2: areturn
- LineNumberTable:
- line 5: 0
- //这个方法的描述到这里就结束了
-```
-接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
-```java
- public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
- descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=1, locals=3, args_size=3
- 0: ldc #2 // String 9527
- 2: areturn
- LineNumberTable:
- line 8: 0
- MethodParameters:
- Name Flags
- canUGetMyName
- yesICan
-```
-可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。
- 下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 :
-```java
-public class ByteCodeParameters {
- public String simpleMethod(String canUGetMyName, Object yesICan) {
- return "9527";
- }
-
- public static void main(String[] args) throws NoSuchMethodException {
- Class> clazz = ByteCodeParameters.class;
- Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
- Parameter[] parameters = simple.getParameters();
- for (Parameter p : parameters) {
- System.out.println(p.getName());
- }
- }
-}
-输出 :
-arg0
-arg1
-```
-???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在
-Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
-己编译一个.class文件放在IDEA的out下,然后再来运行 :
-```java
-输出 :
-canUGetMyName
-yesICan
-```
-这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档]
-(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html)
-
-## 总结与补充
-在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework
-里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。
diff --git a/docs/java/Basis/Arrays,CollectionsCommonMethods.md b/docs/java/basic/Arrays,CollectionsCommonMethods.md
similarity index 92%
rename from docs/java/Basis/Arrays,CollectionsCommonMethods.md
rename to docs/java/basic/Arrays,CollectionsCommonMethods.md
index f3b97c5e..0710de44 100644
--- a/docs/java/Basis/Arrays,CollectionsCommonMethods.md
+++ b/docs/java/basic/Arrays,CollectionsCommonMethods.md
@@ -54,12 +54,12 @@ void rotate(List list, int distance)//旋转。当distance为正数时,将list
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
-
-
+
+
Collections.rotate(arrayList, 4);
System.out.println("Collections.rotate(arrayList, 4):");
System.out.println(arrayList);
-
+
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
@@ -69,7 +69,7 @@ void rotate(List list, int distance)//旋转。当distance为正数时,将list
Collections.shuffle(arrayList);
System.out.println("Collections.shuffle(arrayList):");
System.out.println(arrayList);
-
+
// void swap(List list, int i , int j),交换两个索引位置的元素
Collections.swap(arrayList, 2, 5);
System.out.println("Collections.swap(arrayList, 2, 5):");
@@ -93,7 +93,7 @@ void rotate(List list, int distance)//旋转。当distance为正数时,将list
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
-void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
+void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
@@ -142,7 +142,7 @@ boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换
### 同步控制
-Collectons提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
+Collections提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
@@ -152,9 +152,9 @@ Collectons提供了多个`synchronizedXxx()`方法·,该方法可以将指定
```java
synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。
-synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。
+synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。
synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。
-synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。
+synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。
```
### Collections还可以设置不可变集合,提供了如下三类方法:
@@ -224,7 +224,8 @@ unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合
4. 填充 : `fill()`
5. 转列表: `asList()`
6. 转字符串 : `toString()`
-7.
+7. 复制: `copyOf()`
+
### 排序 : `sort()`
@@ -251,7 +252,7 @@ unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合
System.out.println();
int c[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
- // parallelSort(int[] a) 按照数字顺序排列指定的数组。同sort方法一样也有按范围的排序
+ // parallelSort(int[] a) 按照数字顺序排列指定的数组(并行的)。同sort方法一样也有按范围的排序
Arrays.parallelSort(c);
System.out.println("Arrays.parallelSort(c):");
for (int i : c) {
@@ -285,6 +286,9 @@ System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
```java
// *************查找 binarySearch()****************
char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
+ // 排序后再进行二分查找,否则找不到
+ Arrays.sort(e);
+ System.out.println("Arrays.sort(e)" + Arrays.toString(e));
System.out.println("Arrays.binarySearch(e, 'c'):");
int s = Arrays.binarySearch(e, 'c');
System.out.println("字符c在数组的位置:" + s);
@@ -293,12 +297,12 @@ System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
### 比较: `equals()`
```java
- // *************比较 equals****************
- char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
+ // *************比较 equals****************
+ char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
char[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
/*
- * 元素数量相同,并且相同位置的元素相同。 另外,如果两个数组引用都是null,则它们被认为是相等的 。
- */
+ * 元素数量相同,并且相同位置的元素相同。 另外,如果两个数组引用都是null,则它们被认为是相等的 。
+ */
// 输出true
System.out.println("Arrays.equals(e, f):" + Arrays.equals(e, f));
```
@@ -345,12 +349,12 @@ System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
### 转字符串 `toString()`
```java
- // *************转字符串 toString()****************
- /*
- * 返回指定数组的内容的字符串表示形式。
- */
- char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
- System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]
+ // *************转字符串 toString()****************
+ /*
+ * 返回指定数组的内容的字符串表示形式。
+ */
+ char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
+ System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]
```
### 复制 `copyOf()`
@@ -358,7 +362,7 @@ System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
```java
// *************复制 copy****************
// copyOf 方法实现数组复制,h为数组,6为复制的长度
- int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
+ int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
int i[] = Arrays.copyOf(h, 6);
System.out.println("Arrays.copyOf(h, 6);:");
// 输出结果:123333
@@ -377,4 +381,3 @@ System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
// 换行
System.out.println();
```
-
diff --git a/docs/java/Basis/final、static、this、super.md b/docs/java/basic/final,static,this,super.md
similarity index 88%
rename from docs/java/Basis/final、static、this、super.md
rename to docs/java/basic/final,static,this,super.md
index e9008064..2c2f422b 100644
--- a/docs/java/Basis/final、static、this、super.md
+++ b/docs/java/basic/final,static,this,super.md
@@ -65,8 +65,6 @@ class Manager {
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
-
-
## super 关键字
super关键字用于从子类访问父类的变量和方法。 例如:
@@ -121,12 +119,10 @@ public class Sub extends Super {
HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
-
-
调用格式:
-- 类名.静态变量名
-- 类名.静态方法名()
+- `类名.静态变量名`
+- `类名.静态方法名()`
如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。
@@ -136,15 +132,15 @@ public class Sub extends Super {
public class StaticBean {
String name;
- 静态变量
+ //静态变量
static int age;
public StaticBean(String name) {
this.name = name;
}
- 静态方法
+ //静态方法
static void SayHello() {
- System.out.println(Hello i am java);
+ System.out.println("Hello i am java");
}
@Override
public String toString() {
@@ -206,11 +202,11 @@ Example(静态内部类实现单例模式)
```java
public class Singleton {
- 声明为 private 避免调用默认构造方法创建对象
+ //声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
- 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
+ // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
@@ -234,12 +230,10 @@ public class Singleton {
```java
- Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
- 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可
+ //将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
+ //如果只想导入单一某个静态方法,只需要将换成对应的方法名即可
-import static java.lang.Math.;
-
- 换成import static java.lang.Math.max;具有一样的效果
+import static java.lang.Math.;//换成import static java.lang.Math.max;具有一样的效果
public class Demo {
public static void main(String[] args) {
@@ -285,52 +279,62 @@ class Foo {
- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
-### static{}静态代码块与{}非静态代码块(构造代码块)
+### `static{}`静态代码块与`{}`非静态代码块(构造代码块)
相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
+> 修正 [issue #677](https://github.com/Snailclimb/JavaGuide/issues/677):静态代码块可能在第一次new的时候执行,但不一定只在第一次new的时候执行。比如通过 `Class.forName("ClassDemo")`创建 Class 对象的时候也会执行。
+
一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
-Example
+Example:
```java
public class Test {
public Test() {
- System.out.print(默认构造方法!--);
+ System.out.print("默认构造方法!--");
}
- 非静态代码块
+ //非静态代码块
{
- System.out.print(非静态代码块!--);
- }
- 静态代码块
- static {
- System.out.print(静态代码块!--);
+ System.out.print("非静态代码块!--");
}
- public static void test() {
- System.out.print(静态方法中的内容! --);
+ //静态代码块
+ static {
+ System.out.print("静态代码块!--");
+ }
+
+ private static void test() {
+ System.out.print("静态方法中的内容! --");
{
- System.out.print(静态方法中的代码块!--);
+ System.out.print("静态方法中的代码块!--");
}
}
- public static void main(String[] args) {
- Test test = new Test();
- Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!--
+ public static void main(String[] args) {
+ Test test = new Test();
+ Test.test();//静态代码块!--静态方法中的内容! --静态方法中的代码块!--
}
+}
```
-当执行 `Test.test();` 时输出:
+上述代码输出:
+
+```
+静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!--
+```
+
+当只执行 `Test.test();` 时输出:
```
静态代码块!--静态方法中的内容! --静态方法中的代码块!--
```
-当执行 `Test test = new Test();` 时输出:
+当只执行 `Test test = new Test();` 时输出:
```
静态代码块!--非静态代码块!--默认构造方法!--
diff --git a/docs/java/basic/reflection.md b/docs/java/basic/reflection.md
new file mode 100644
index 00000000..6c8c6b8c
--- /dev/null
+++ b/docs/java/basic/reflection.md
@@ -0,0 +1,136 @@
+### 反射机制介绍
+
+JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
+
+### 获取 Class 对象的两种方式
+
+如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了两种方式获取 Class 对象:
+
+1.知道具体类的情况下可以使用:
+
+```java
+Class alunbarClass = TargetObject.class;
+```
+
+但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象
+
+2.通过 `Class.forName()`传入类的路径获取:
+
+```java
+Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
+```
+
+### 代码实例
+
+**简单用代码演示一下反射的一些操作!**
+
+1.创建一个我们要使用反射操作的类 `TargetObject`:
+
+```java
+package cn.javaguide;
+
+public class TargetObject {
+ private String value;
+
+ public TargetObject() {
+ value = "JavaGuide";
+ }
+
+ public void publicMethod(String s) {
+ System.out.println("I love " + s);
+ }
+
+ private void privateMethod() {
+ System.out.println("value is " + value);
+ }
+}
+```
+
+2.使用反射操作这个类的方法以及参数
+
+```java
+package cn.javaguide;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Main {
+ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
+ /**
+ * 获取TargetObject类的Class对象并且创建TargetObject类实例
+ */
+ Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
+ TargetObject targetObject = (TargetObject) tagetClass.newInstance();
+ /**
+ * 获取所有类中所有定义的方法
+ */
+ Method[] methods = tagetClass.getDeclaredMethods();
+ for (Method method : methods) {
+ System.out.println(method.getName());
+ }
+ /**
+ * 获取指定方法并调用
+ */
+ Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
+ String.class);
+
+ publicMethod.invoke(targetObject, "JavaGuide");
+ /**
+ * 获取指定参数并对参数进行修改
+ */
+ Field field = tagetClass.getDeclaredField("value");
+ //为了对类中的参数进行修改我们取消安全检查
+ field.setAccessible(true);
+ field.set(targetObject, "JavaGuide");
+ /**
+ * 调用 private 方法
+ */
+ Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
+ //为了调用private方法我们取消安全检查
+ privateMethod.setAccessible(true);
+ privateMethod.invoke(targetObject);
+ }
+}
+
+```
+
+输出内容:
+
+```
+publicMethod
+privateMethod
+I love JavaGuide
+value is JavaGuide
+```
+
+### 静态编译和动态编译
+
+- **静态编译:**在编译时确定类型,绑定对象
+- **动态编译:**运行时确定类型,绑定对象
+
+### 反射机制优缺点
+
+- **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
+- **缺点:** 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。
+
+### 反射的应用场景
+
+**反射是框架设计的灵魂。**
+
+在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
+
+举例:
+
+1. 我们在使用 JDBC 连接数据库时使用 `Class.forName()`通过反射加载数据库的驱动程序;
+2. Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
+3. 动态配置实例的属性;
+4. ......
+
+**推荐阅读:**
+
+- [Java反射使用总结]( https://zhuanlan.zhihu.com/p/80519709)
+- [Reflection:Java 反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral)
+- [Java 基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078)
+
+##
\ No newline at end of file
diff --git a/docs/java/basic/用好Java中的枚举真的没有那么简单.md b/docs/java/basic/用好Java中的枚举真的没有那么简单.md
new file mode 100644
index 00000000..20b02297
--- /dev/null
+++ b/docs/java/basic/用好Java中的枚举真的没有那么简单.md
@@ -0,0 +1,561 @@
+> 最近重看 Java 枚举,看到这篇觉得还不错的文章,于是简单翻译和完善了一些内容,分享给大家,希望你们也能有所收获。另外,不要忘了文末还有补充哦!
+>
+> ps: 这里发一篇枚举的文章,也是因为后面要发一篇非常实用的关于 SpringBoot 全局异常处理的比较好的实践,里面就用到了枚举。
+>
+> 这篇文章由 JavaGuide 翻译,公众号: JavaGuide,原文地址:https://www.baeldung.com/a-guide-to-java-enums 。
+>
+> 转载请注明上面这段文字。
+
+## 1.概览
+
+在本文中,我们将看到什么是 Java 枚举,它们解决了哪些问题以及如何在实践中使用 Java 枚举实现一些设计模式。
+
+enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类,更多内容可以自行查看其[官方文档](https://docs.oracle.com/javase/6/docs/api/java/lang/Enum.html)。
+
+枚举在很多时候会和常量拿来对比,可能因为本身我们大量实际使用枚举的地方就是为了替代常量。那么这种方式由什么优势呢?
+
+**以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。**
+
+下面示例定义一个简单的枚举类型 pizza 订单的状态,共有三种 ORDERED, READY, DELIVERED状态:
+
+```java
+package shuang.kou.enumdemo.enumtest;
+
+public enum PizzaStatus {
+ ORDERED,
+ READY,
+ DELIVERED;
+}
+```
+
+**简单来说,我们通过上面的代码避免了定义常量,我们将所有和 pizza 订单的状态的常量都统一放到了一个枚举类型里面。**
+
+```java
+System.out.println(PizzaStatus.ORDERED.name());//ORDERED
+System.out.println(PizzaStatus.ORDERED);//ORDERED
+System.out.println(PizzaStatus.ORDERED.name().getClass());//class java.lang.String
+System.out.println(PizzaStatus.ORDERED.getClass());//class shuang.kou.enumdemo.enumtest.PizzaStatus
+```
+
+## 2.自定义枚举方法
+
+现在我们对枚举是什么以及如何使用它们有了基本的了解,让我们通过在枚举上定义一些额外的API方法,将上一个示例提升到一个新的水平:
+
+```java
+public class Pizza {
+ private PizzaStatus status;
+ public enum PizzaStatus {
+ ORDERED,
+ READY,
+ DELIVERED;
+ }
+
+ public boolean isDeliverable() {
+ if (getStatus() == PizzaStatus.READY) {
+ return true;
+ }
+ return false;
+ }
+
+ // Methods that set and get the status variable.
+}
+```
+
+## 3.使用 == 比较枚举类型
+
+由于枚举类型确保JVM中仅存在一个常量实例,因此我们可以安全地使用“ ==”运算符比较两个变量,如上例所示;此外,“ ==”运算符可提供编译时和运行时的安全性。
+
+首先,让我们看一下以下代码段中的运行时安全性,其中“ ==”运算符用于比较状态,并且如果两个值均为null 都不会引发 NullPointerException。相反,如果使用equals方法,将抛出 NullPointerException:
+
+```java
+if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED));
+if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
+```
+
+对于编译时安全性,我们看另一个示例,两个不同枚举类型进行比较,使用equal方法比较结果确定为true,因为getStatus方法的枚举值与另一个类型枚举值一致,但逻辑上应该为false。这个问题可以使用==操作符避免。因为编译器会表示类型不兼容错误:
+
+```java
+if(testPz.getStatus().equals(TestColor.GREEN));
+if(testPz.getStatus() == TestColor.GREEN);
+```
+
+## 4.在 switch 语句中使用枚举类型
+
+```java
+public int getDeliveryTimeInDays() {
+ switch (status) {
+ case ORDERED: return 5;
+ case READY: return 2;
+ case DELIVERED: return 0;
+ }
+ return 0;
+}
+```
+
+## 5.枚举类型的属性,方法和构造函数
+
+> 文末有我(JavaGuide)的补充。
+
+你可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。
+
+下面,让我们扩展上面的示例,实现从比萨的一个阶段到另一个阶段的过渡,并了解如何摆脱之前使用的if语句和switch语句:
+
+```java
+public class Pizza {
+
+ private PizzaStatus status;
+ public enum PizzaStatus {
+ ORDERED (5){
+ @Override
+ public boolean isOrdered() {
+ return true;
+ }
+ },
+ READY (2){
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+ },
+ DELIVERED (0){
+ @Override
+ public boolean isDelivered() {
+ return true;
+ }
+ };
+
+ private int timeToDelivery;
+
+ public boolean isOrdered() {return false;}
+
+ public boolean isReady() {return false;}
+
+ public boolean isDelivered(){return false;}
+
+ public int getTimeToDelivery() {
+ return timeToDelivery;
+ }
+
+ PizzaStatus (int timeToDelivery) {
+ this.timeToDelivery = timeToDelivery;
+ }
+ }
+
+ public boolean isDeliverable() {
+ return this.status.isReady();
+ }
+
+ public void printTimeToDeliver() {
+ System.out.println("Time to delivery is " +
+ this.getStatus().getTimeToDelivery());
+ }
+
+ // Methods that set and get the status variable.
+}
+```
+
+下面这段代码展示它是如何 work 的:
+
+```java
+@Test
+public void givenPizaOrder_whenReady_thenDeliverable() {
+ Pizza testPz = new Pizza();
+ testPz.setStatus(Pizza.PizzaStatus.READY);
+ assertTrue(testPz.isDeliverable());
+}
+```
+
+## 6.EnumSet and EnumMap
+
+### 6.1. EnumSet
+
+`EnumSet` 是一种专门为枚举类型所设计的 `Set` 类型。
+
+与`HashSet`相比,由于使用了内部位向量表示,因此它是特定 `Enum` 常量集的非常有效且紧凑的表示形式。
+
+它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码。
+
+`EnumSet` 是抽象类,其有两个实现:`RegularEnumSet` 、`JumboEnumSet`,选择哪一个取决于实例化时枚举中常量的数量。
+
+在很多场景中的枚举常量集合操作(如:取子集、增加、删除、`containsAll`和`removeAll`批操作)使用`EnumSet`非常合适;如果需要迭代所有可能的常量则使用`Enum.values()`。
+
+```java
+public class Pizza {
+
+ private static EnumSet undeliveredPizzaStatuses =
+ EnumSet.of(PizzaStatus.ORDERED, PizzaStatus.READY);
+
+ private PizzaStatus status;
+
+ public enum PizzaStatus {
+ ...
+ }
+
+ public boolean isDeliverable() {
+ return this.status.isReady();
+ }
+
+ public void printTimeToDeliver() {
+ System.out.println("Time to delivery is " +
+ this.getStatus().getTimeToDelivery() + " days");
+ }
+
+ public static List getAllUndeliveredPizzas(List input) {
+ return input.stream().filter(
+ (s) -> undeliveredPizzaStatuses.contains(s.getStatus()))
+ .collect(Collectors.toList());
+ }
+
+ public void deliver() {
+ if (isDeliverable()) {
+ PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
+ .deliver(this);
+ this.setStatus(PizzaStatus.DELIVERED);
+ }
+ }
+
+ // Methods that set and get the status variable.
+}
+```
+
+ 下面的测试演示了展示了 `EnumSet` 在某些场景下的强大功能:
+
+```java
+@Test
+public void givenPizaOrders_whenRetrievingUnDeliveredPzs_thenCorrectlyRetrieved() {
+ List pzList = new ArrayList<>();
+ Pizza pz1 = new Pizza();
+ pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
+
+ Pizza pz2 = new Pizza();
+ pz2.setStatus(Pizza.PizzaStatus.ORDERED);
+
+ Pizza pz3 = new Pizza();
+ pz3.setStatus(Pizza.PizzaStatus.ORDERED);
+
+ Pizza pz4 = new Pizza();
+ pz4.setStatus(Pizza.PizzaStatus.READY);
+
+ pzList.add(pz1);
+ pzList.add(pz2);
+ pzList.add(pz3);
+ pzList.add(pz4);
+
+ List undeliveredPzs = Pizza.getAllUndeliveredPizzas(pzList);
+ assertTrue(undeliveredPzs.size() == 3);
+}
+```
+
+### 6.2. EnumMap
+
+`EnumMap`是一个专门化的映射实现,用于将枚举常量用作键。与对应的 `HashMap` 相比,它是一个高效紧凑的实现,并且在内部表示为一个数组:
+
+```java
+EnumMap map;
+```
+
+让我们快速看一个真实的示例,该示例演示如何在实践中使用它:
+
+```java
+public static EnumMap>
+ groupPizzaByStatus(List pizzaList) {
+ EnumMap> pzByStatus =
+ new EnumMap>(PizzaStatus.class);
+
+ for (Pizza pz : pizzaList) {
+ PizzaStatus status = pz.getStatus();
+ if (pzByStatus.containsKey(status)) {
+ pzByStatus.get(status).add(pz);
+ } else {
+ List newPzList = new ArrayList();
+ newPzList.add(pz);
+ pzByStatus.put(status, newPzList);
+ }
+ }
+ return pzByStatus;
+}
+```
+
+ 下面的测试演示了展示了 `EnumMap` 在某些场景下的强大功能:
+
+```java
+@Test
+public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
+ List pzList = new ArrayList<>();
+ Pizza pz1 = new Pizza();
+ pz1.setStatus(Pizza.PizzaStatus.DELIVERED);
+
+ Pizza pz2 = new Pizza();
+ pz2.setStatus(Pizza.PizzaStatus.ORDERED);
+
+ Pizza pz3 = new Pizza();
+ pz3.setStatus(Pizza.PizzaStatus.ORDERED);
+
+ Pizza pz4 = new Pizza();
+ pz4.setStatus(Pizza.PizzaStatus.READY);
+
+ pzList.add(pz1);
+ pzList.add(pz2);
+ pzList.add(pz3);
+ pzList.add(pz4);
+
+ EnumMap> map = Pizza.groupPizzaByStatus(pzList);
+ assertTrue(map.get(Pizza.PizzaStatus.DELIVERED).size() == 1);
+ assertTrue(map.get(Pizza.PizzaStatus.ORDERED).size() == 2);
+ assertTrue(map.get(Pizza.PizzaStatus.READY).size() == 1);
+}
+```
+
+## 7. 通过枚举实现一些设计模式
+
+### 7.1 单例模式
+
+通常,使用类实现 Singleton 模式并非易事,枚举提供了一种实现单例的简便方法。
+
+《Effective Java 》和《Java与模式》都非常推荐这种方式,使用这种方式方式实现枚举可以有什么好处呢?
+
+《Effective Java》
+
+> 这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton的最佳方法。 —-《Effective Java 中文版 第二版》
+
+《Java与模式》
+
+> 《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
+
+下面的代码段显示了如何使用枚举实现单例模式:
+
+```java
+public enum PizzaDeliverySystemConfiguration {
+ INSTANCE;
+ PizzaDeliverySystemConfiguration() {
+ // Initialization configuration which involves
+ // overriding defaults like delivery strategy
+ }
+
+ private PizzaDeliveryStrategy deliveryStrategy = PizzaDeliveryStrategy.NORMAL;
+
+ public static PizzaDeliverySystemConfiguration getInstance() {
+ return INSTANCE;
+ }
+
+ public PizzaDeliveryStrategy getDeliveryStrategy() {
+ return deliveryStrategy;
+ }
+}
+```
+
+如何使用呢?请看下面的代码:
+
+```java
+PizzaDeliveryStrategy deliveryStrategy = PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy();
+```
+
+通过 `PizzaDeliverySystemConfiguration.getInstance()` 获取的就是单例的 `PizzaDeliverySystemConfiguration`
+
+### 7.2 策略模式
+
+通常,策略模式由不同类实现同一个接口来实现的。
+
+ 这也就意味着添加新策略意味着添加新的实现类。使用枚举,可以轻松完成此任务,添加新的实现意味着只定义具有某个实现的另一个实例。
+
+下面的代码段显示了如何使用枚举实现策略模式:
+
+```java
+public enum PizzaDeliveryStrategy {
+ EXPRESS {
+ @Override
+ public void deliver(Pizza pz) {
+ System.out.println("Pizza will be delivered in express mode");
+ }
+ },
+ NORMAL {
+ @Override
+ public void deliver(Pizza pz) {
+ System.out.println("Pizza will be delivered in normal mode");
+ }
+ };
+
+ public abstract void deliver(Pizza pz);
+}
+```
+
+给 `Pizza `增加下面的方法:
+
+```java
+public void deliver() {
+ if (isDeliverable()) {
+ PizzaDeliverySystemConfiguration.getInstance().getDeliveryStrategy()
+ .deliver(this);
+ this.setStatus(PizzaStatus.DELIVERED);
+ }
+}
+```
+
+如何使用呢?请看下面的代码:
+
+```java
+@Test
+public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges() {
+ Pizza pz = new Pizza();
+ pz.setStatus(Pizza.PizzaStatus.READY);
+ pz.deliver();
+ assertTrue(pz.getStatus() == Pizza.PizzaStatus.DELIVERED);
+}
+```
+
+## 8. Java 8 与枚举
+
+Pizza 类可以用Java 8重写,您可以看到方法 lambda 和Stream API如何使 `getAllUndeliveredPizzas()`和`groupPizzaByStatus()`方法变得如此简洁:
+
+`getAllUndeliveredPizzas()`:
+
+```java
+public static List getAllUndeliveredPizzas(List input) {
+ return input.stream().filter(
+ (s) -> !deliveredPizzaStatuses.contains(s.getStatus()))
+ .collect(Collectors.toList());
+}
+```
+
+`groupPizzaByStatus()` :
+
+```java
+public static EnumMap>
+ groupPizzaByStatus(List pzList) {
+ EnumMap> map = pzList.stream().collect(
+ Collectors.groupingBy(Pizza::getStatus,
+ () -> new EnumMap<>(PizzaStatus.class), Collectors.toList()));
+ return map;
+}
+```
+
+## 9. Enum 类型的 JSON 表现形式
+
+使用Jackson库,可以将枚举类型的JSON表示为POJO。下面的代码段显示了可以用于同一目的的Jackson批注:
+
+```java
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum PizzaStatus {
+ ORDERED (5){
+ @Override
+ public boolean isOrdered() {
+ return true;
+ }
+ },
+ READY (2){
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+ },
+ DELIVERED (0){
+ @Override
+ public boolean isDelivered() {
+ return true;
+ }
+ };
+
+ private int timeToDelivery;
+
+ public boolean isOrdered() {return false;}
+
+ public boolean isReady() {return false;}
+
+ public boolean isDelivered(){return false;}
+
+ @JsonProperty("timeToDelivery")
+ public int getTimeToDelivery() {
+ return timeToDelivery;
+ }
+
+ private PizzaStatus (int timeToDelivery) {
+ this.timeToDelivery = timeToDelivery;
+ }
+}
+```
+
+我们可以按如下方式使用 `Pizza` 和 `PizzaStatus`:
+
+```java
+Pizza pz = new Pizza();
+pz.setStatus(Pizza.PizzaStatus.READY);
+System.out.println(Pizza.getJsonString(pz));
+```
+
+生成 Pizza 状态以以下JSON展示:
+
+```json
+{
+ "status" : {
+ "timeToDelivery" : 2,
+ "ready" : true,
+ "ordered" : false,
+ "delivered" : false
+ },
+ "deliverable" : true
+}
+```
+
+有关枚举类型的JSON序列化/反序列化(包括自定义)的更多信息,请参阅[Jackson-将枚举序列化为JSON对象。](https://www.baeldung.com/jackson-serialize-enums)
+
+## 10.总结
+
+本文我们讨论了Java枚举类型,从基础知识到高级应用以及实际应用场景,让我们感受到枚举的强大功能。
+
+## 11. 补充
+
+我们在上面讲到了,我们可以通过在枚举类型中定义属性,方法和构造函数让它变得更加强大。
+
+下面我通过一个实际的例子展示一下,当我们调用短信验证码的时候可能有几种不同的用途,我们在下面这样定义:
+
+```java
+
+public enum PinType {
+
+ REGISTER(100000, "注册使用"),
+ FORGET_PASSWORD(100001, "忘记密码使用"),
+ UPDATE_PHONE_NUMBER(100002, "更新手机号码使用");
+
+ private final int code;
+ private final String message;
+
+ PinType(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "PinType{" +
+ "code=" + code +
+ ", message='" + message + '\'' +
+ '}';
+ }
+}
+```
+
+实际使用:
+
+ ```java
+System.out.println(PinType.FORGET_PASSWORD.getCode());
+System.out.println(PinType.FORGET_PASSWORD.getMessage());
+System.out.println(PinType.FORGET_PASSWORD.toString());
+ ```
+
+Output:
+
+```java
+100001
+忘记密码使用
+PinType{code=100001, message='忘记密码使用'}
+```
+
+这样的话,在实际使用起来就会非常灵活方便!
\ No newline at end of file
diff --git a/docs/java/ArrayList-Grow.md b/docs/java/collection/ArrayList-Grow.md
similarity index 92%
rename from docs/java/ArrayList-Grow.md
rename to docs/java/collection/ArrayList-Grow.md
index 6dd4cc93..0a758cca 100644
--- a/docs/java/ArrayList-Grow.md
+++ b/docs/java/collection/ArrayList-Grow.md
@@ -145,7 +145,7 @@
}
```
-**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后)** JDk1.6版本时,扩容之后容量为 1.5 倍+1!详情请参考源码
+**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity为偶数就是1.5倍,否则是1.5倍左右)!** 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.
> ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
@@ -270,7 +270,6 @@ public class ArrayscopyOfTest {
10
```
-
### 3.3 两者联系和区别
**联系:**
@@ -281,8 +280,6 @@ public class ArrayscopyOfTest {
`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。
-
-
## 四 `ensureCapacity`方法
ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢?
@@ -324,14 +321,6 @@ public class EnsureCapacityTest {
long endTime = System.currentTimeMillis();
System.out.println("使用ensureCapacity方法前:"+(endTime - startTime));
- list = new ArrayList();
- long startTime1 = System.currentTimeMillis();
- list.ensureCapacity(N);
- for (int i = 0; i < N; i++) {
- list.add(i);
- }
- long endTime1 = System.currentTimeMillis();
- System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
}
}
```
@@ -339,9 +328,31 @@ public class EnsureCapacityTest {
运行结果:
```
-使用ensureCapacity方法前:4637
-使用ensureCapacity方法后:241
+使用ensureCapacity方法前:2158
+```
+
+```java
+public class EnsureCapacityTest {
+ public static void main(String[] args) {
+ ArrayList list = new ArrayList();
+ final int N = 10000000;
+ list = new ArrayList();
+ long startTime1 = System.currentTimeMillis();
+ list.ensureCapacity(N);
+ for (int i = 0; i < N; i++) {
+ list.add(i);
+ }
+ long endTime1 = System.currentTimeMillis();
+ System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1));
+ }
+}
+```
+
+运行结果:
```
-通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数
+使用ensureCapacity方法前:1773
+```
+
+通过运行结果,我们可以看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数。
diff --git a/docs/java/ArrayList.md b/docs/java/collection/ArrayList.md
similarity index 98%
rename from docs/java/ArrayList.md
rename to docs/java/collection/ArrayList.md
index c3e8dd47..f6578a7a 100644
--- a/docs/java/ArrayList.md
+++ b/docs/java/collection/ArrayList.md
@@ -660,7 +660,7 @@ public class ArrayList extends AbstractList
(3)private class SubList extends AbstractList implements RandomAccess
(4)static final class ArrayListSpliterator implements Spliterator
```
- ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。
+ ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**, **next()**, **remove()** 等方法;其中的**ListItr** 继承 **Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**, **nextIndex()**, **previousIndex()**, **previous()**, **set(E e)**, **add(E e)** 等方法,所以这也可以看出了 **Iterator和ListIterator的区别:** ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。
### ArrayList经典Demo
```java
diff --git a/docs/java/HashMap.md b/docs/java/collection/HashMap.md
similarity index 95%
rename from docs/java/HashMap.md
rename to docs/java/collection/HashMap.md
index 45fad50c..c365065c 100644
--- a/docs/java/HashMap.md
+++ b/docs/java/collection/HashMap.md
@@ -18,7 +18,7 @@
## HashMap 简介
HashMap 主要用来存放键值对,它基于哈希表的Map接口实现 ,是常用的Java集合之一。
-JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。
+JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间,具体可以参考 `treeifyBin`方法。
## 底层数据结构分析
### JDK1.8之前
@@ -56,7 +56,7 @@ static int hash(int h) {
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
+
### JDK1.8之后
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
@@ -170,7 +170,9 @@ static final class TreeNode extends LinkedHashMap.Entry {
```
## HashMap源码分析
### 构造方法
-
+
+HashMap 中有四个构造方法,它们分别如下:
+
```java
// 默认构造函数。
public HashMap() {
@@ -235,11 +237,11 @@ HashMap只提供了put用于添加元素,putVal方法只是给put方法调用
**对putVal方法添加元素的分析如下:**
- ①如果定位到的数组位置没有元素 就直接插入。
-- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入。
+- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。
+ps:下图有一个小问题,来自 [issue#608](https://github.com/Snailclimb/JavaGuide/issues/608)指出:直接覆盖之后应该就会 return,不会有后续操作。参考 JDK8 HashMap.java 658 行。
-
-
+
```java
public V put(K key, V value) {
@@ -510,7 +512,8 @@ public class HashMapDemo {
}
/**
- * 另外一种不常用的遍历方式
+ * 如果既要遍历key又要value,那么建议这种方式,应为如果先获取keySet然后再执行map.get(key),map内部会执行两次遍历。
+ * 一次是在获取keySet的时候,一次是在遍历所有key的时候。
*/
// 当我调用put(key,value)方法的时候,首先会把key和value封装到
// Entry这个静态内部类对象中,把Entry对象再添加到数组中,所以我们想获取
diff --git a/docs/java/collection/Java集合框架常见面试题.md b/docs/java/collection/Java集合框架常见面试题.md
new file mode 100644
index 00000000..c664ca8f
--- /dev/null
+++ b/docs/java/collection/Java集合框架常见面试题.md
@@ -0,0 +1,456 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [剖析面试最常见问题之Java集合框架](#剖析面试最常见问题之java集合框架)
+ - [说说List,Set,Map三者的区别?](#说说listsetmap三者的区别)
+ - [Arraylist 与 LinkedList 区别?](#arraylist-与-linkedlist-区别)
+ - [补充内容:RandomAccess接口](#补充内容randomaccess接口)
+ - [补充内容:双向链表和双向循环链表](#补充内容双向链表和双向循环链表)
+ - [ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢?](#arraylist-与-vector-区别呢为什么要用arraylist取代vector呢)
+ - [说一说 ArrayList 的扩容机制吧](#说一说-arraylist-的扩容机制吧)
+ - [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
+ - [HashMap 和 HashSet区别](#hashmap-和-hashset区别)
+ - [HashSet如何检查重复](#hashset如何检查重复)
+ - [HashMap的底层实现](#hashmap的底层实现)
+ - [JDK1.8之前](#jdk18之前)
+ - [JDK1.8之后](#jdk18之后)
+ - [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)
+ - [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)
+ - [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)
+ - [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)
+ - [JDK1.7(上面有示意图)](#jdk17上面有示意图)
+ - [JDK1.8 (上面有示意图)](#jdk18-上面有示意图)
+ - [comparable 和 Comparator的区别](#comparable-和-comparator的区别)
+ - [Comparator定制排序](#comparator定制排序)
+ - [重写compareTo方法实现按年龄来排序](#重写compareto方法实现按年龄来排序)
+ - [集合框架底层数据结构总结](#集合框架底层数据结构总结)
+ - [Collection](#collection)
+ - [1. List](#1-list)
+ - [2. Set](#2-set)
+ - [Map](#map)
+ - [如何选用集合?](#如何选用集合)
+
+
+
+# 剖析面试最常见问题之Java集合框架
+
+## 说说List,Set,Map三者的区别?
+
+- **List(对付顺序的好帮手):** List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
+- **Set(注重独一无二的性质):** 不允许重复的集合。不会有多个元素引用相同的对象。
+- **Map(用Key来搜索的专家):** 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
+
+## Arraylist 与 LinkedList 区别?
+
+- **1. 是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全;
+
+- **2. 底层数据结构:** `Arraylist` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
+
+- **3. 插入和删除是否受元素位置的影响:** ① **`ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **`LinkedList` 采用链表存储,所以对于`add(E e)`方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置`i`插入和删除元素的话(`(add(int index, E element)`) 时间复杂度近似为`o(n))`因为需要先移动到指定位置再插入。**
+
+- **4. 是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
+
+- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
+
+### **补充内容:RandomAccess接口**
+
+```java
+public interface RandomAccess {
+}
+```
+
+查看源码我们发现实际上 `RandomAccess` 接口中什么都没有定义。所以,在我看来 `RandomAccess` 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
+
+在 `binarySearch()` 方法中,它要判断传入的list 是否 `RamdomAccess` 的实例,如果是,调用`indexedBinarySearch()`方法,如果不是,那么调用`iteratorBinarySearch()`方法
+
+```java
+ public static
+ int binarySearch(List extends Comparable super T>> list, T key) {
+ if (list instanceof RandomAccess || list.size() MAXIMUM_CAPACITY)
+ initialCapacity = MAXIMUM_CAPACITY;
+ if (loadFactor <= 0 || Float.isNaN(loadFactor))
+ throw new IllegalArgumentException("Illegal load factor: " +
+ loadFactor);
+ this.loadFactor = loadFactor;
+ this.threshold = tableSizeFor(initialCapacity);
+ }
+ public HashMap(int initialCapacity) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR);
+ }
+```
+
+下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
+
+```java
+ /**
+ * Returns a power of two size for the given target capacity.
+ */
+ static final int tableSizeFor(int cap) {
+ int n = cap - 1;
+ n |= n >>> 1;
+ n |= n >>> 2;
+ n |= n >>> 4;
+ n |= n >>> 8;
+ n |= n >>> 16;
+ return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
+ }
+```
+
+## HashMap 和 HashSet区别
+
+如果你看过 `HashSet` 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 `clone() `、`writeObject()`、`readObject()`是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
+
+| HashMap | HashSet |
+| :------------------------------: | :----------------------------------------------------------: |
+| 实现了Map接口 | 实现Set接口 |
+| 存储键值对 | 仅存储对象 |
+| 调用 `put()`向map中添加元素 | 调用 `add()`方法向Set中添加元素 |
+| HashMap使用键(Key)计算Hashcode | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性, |
+
+## HashSet如何检查重复
+
+当你把对象加入`HashSet`时,HashSet会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用`equals()`方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。(摘自我的Java启蒙书《Head fist java》第二版)
+
+**hashCode()与equals()的相关规定:**
+
+1. 如果两个对象相等,则hashcode一定也是相同的
+2. 两个对象相等,对两个equals方法返回true
+3. 两个对象有相同的hashcode值,它们也不一定是相等的
+4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
+5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
+
+**==与equals的区别**
+
+1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
+2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
+3. ==指引用是否相同 equals()指的是值是否相同
+
+## HashMap的底层实现
+
+### JDK1.8之前
+
+JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
+
+**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
+
+**JDK 1.8 HashMap 的 hash 方法源码:**
+
+JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
+
+```java
+ static final int hash(Object key) {
+ int h;
+ // key.hashCode():返回散列值也就是hashcode
+ // ^ :按位异或
+ // >>>:无符号右移,忽略符号位,空位都以0补齐
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
+ }
+```
+
+对比一下 JDK1.7的 HashMap 的 hash 方法源码.
+
+```java
+static int hash(int h) {
+ // This function ensures that hashCodes that differ only by
+ // constant multiples at each bit position have a bounded
+ // number of collisions (approximately 8 at default load factor).
+
+ h ^= (h >>> 20) ^ (h >>> 12);
+ return h ^ (h >>> 7) ^ (h >>> 4);
+}
+```
+
+相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
+
+所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
+
+
+
+### JDK1.8之后
+
+相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
+
+
+
+> TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
+
+**推荐阅读:**
+
+- 《Java 8系列之重新认识HashMap》 :
+
+## HashMap 的长度为什么是2的幂次方
+
+为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。
+
+**这个算法应该如何设计呢?**
+
+我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。**
+
+## HashMap 多线程操作导致死循环问题
+
+主要原因在于并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。
+
+详情请查看:
+
+## ConcurrentHashMap 和 Hashtable 的区别
+
+ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
+
+- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
+- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
+
+**两者的对比图:**
+
+图片来源:
+
+**HashTable:**
+
+
+
+**JDK1.7的ConcurrentHashMap:**
+
+
+
+**JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):**
+
+
+
+## ConcurrentHashMap线程安全的具体实现方式/底层具体实现
+
+### JDK1.7(上面有示意图)
+
+首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
+
+**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
+
+Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
+
+```java
+static class Segment extends ReentrantLock implements Serializable {
+}
+```
+
+一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
+
+### JDK1.8 (上面有示意图)
+
+ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)))
+
+synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
+
+## comparable 和 Comparator的区别
+
+- comparable接口实际上是出自java.lang包 它有一个 `compareTo(Object obj)`方法用来排序
+- comparator接口实际上是出自 java.util 包它有一个`compare(Object obj1, Object obj2)`方法用来排序
+
+一般我们需要对一个集合使用自定义排序时,我们就要重写`compareTo()`方法或`compare()`方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写`compareTo()`方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 `Collections.sort()`.
+
+### Comparator定制排序
+
+```java
+ ArrayList arrayList = new ArrayList();
+ arrayList.add(-1);
+ arrayList.add(3);
+ arrayList.add(3);
+ arrayList.add(-5);
+ arrayList.add(7);
+ arrayList.add(4);
+ arrayList.add(-9);
+ arrayList.add(-7);
+ System.out.println("原始数组:");
+ System.out.println(arrayList);
+ // void reverse(List list):反转
+ Collections.reverse(arrayList);
+ System.out.println("Collections.reverse(arrayList):");
+ System.out.println(arrayList);
+
+ // void sort(List list),按自然排序的升序排序
+ Collections.sort(arrayList);
+ System.out.println("Collections.sort(arrayList):");
+ System.out.println(arrayList);
+ // 定制排序的用法
+ Collections.sort(arrayList, new Comparator() {
+
+ @Override
+ public int compare(Integer o1, Integer o2) {
+ return o2.compareTo(o1);
+ }
+ });
+ System.out.println("定制排序后:");
+ System.out.println(arrayList);
+```
+
+Output:
+
+```
+原始数组:
+[-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]
+```
+
+### 重写compareTo方法实现按年龄来排序
+
+```java
+// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
+// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
+// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
+
+public class Person implements Comparable {
+ private String name;
+ private int age;
+
+ public Person(String name, int age) {
+ super();
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ /**
+ * TODO重写compareTo方法实现按年龄来排序
+ */
+ @Override
+ public int compareTo(Person o) {
+ // TODO Auto-generated method stub
+ if (this.age > o.getAge()) {
+ return 1;
+ } else if (this.age < o.getAge()) {
+ return -1;
+ }
+ return age;
+ }
+}
+
+```
+
+```java
+ public static void main(String[] args) {
+ TreeMap pdata = new TreeMap();
+ pdata.put(new Person("张三", 30), "zhangsan");
+ pdata.put(new Person("李四", 20), "lisi");
+ pdata.put(new Person("王五", 10), "wangwu");
+ pdata.put(new Person("小红", 5), "xiaohong");
+ // 得到key的值的同时得到key所对应的值
+ Set keys = pdata.keySet();
+ for (Person key : keys) {
+ System.out.println(key.getAge() + "-" + key.getName());
+
+ }
+ }
+```
+
+Output:
+
+```
+5-小红
+10-王五
+20-李四
+30-张三
+```
+
+## 集合框架底层数据结构总结
+
+### Collection
+
+#### 1. List
+
+- **Arraylist:** Object数组
+- **Vector:** Object数组
+- **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
+
+#### 2. Set
+
+- **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素
+- **LinkedHashSet:** LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
+- **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树)
+
+### Map
+
+- **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
+- **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931)
+- **Hashtable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
+- **TreeMap:** 红黑树(自平衡的排序二叉树)
+
+## 如何选用集合?
+
+主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/LinkedList.md b/docs/java/collection/LinkedList.md
similarity index 99%
rename from docs/java/LinkedList.md
rename to docs/java/collection/LinkedList.md
index 983c1fae..d26bc752 100644
--- a/docs/java/LinkedList.md
+++ b/docs/java/collection/LinkedList.md
@@ -458,7 +458,7 @@ public class LinkedListDemo {
linkedList.add(3);
linkedList.removeFirstOccurrence(3); // 从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表)
System.out.println("After removeFirstOccurrence(3):" + linkedList);
- linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表)
+ linkedList.removeLastOccurrence(3); // 从此列表中移除最后一次出现的指定元素(从尾部到头部遍历列表)
System.out.println("After removeFirstOccurrence(3):" + linkedList);
/************************** 遍历操作 ************************/
diff --git a/docs/java/images/exception-architechture-java.png b/docs/java/images/exception-architechture-java.png
new file mode 100644
index 00000000..9a9e3afd
Binary files /dev/null and b/docs/java/images/exception-architechture-java.png differ
diff --git a/docs/java/images/java-exception-handling-class-hierarchy-diagram.jpg b/docs/java/images/java-exception-handling-class-hierarchy-diagram.jpg
new file mode 100644
index 00000000..6b5b7454
Binary files /dev/null and b/docs/java/images/java-exception-handling-class-hierarchy-diagram.jpg differ
diff --git a/docs/java/java-naming-conventions.md b/docs/java/java-naming-conventions.md
new file mode 100644
index 00000000..d205f5f5
--- /dev/null
+++ b/docs/java/java-naming-conventions.md
@@ -0,0 +1,416 @@
+> 原文链接:https://www.cnblogs.com/liqiangchn/p/12000361.html
+
+简洁清爽的代码风格应该是大多数工程师所期待的。在工作中笔者常常因为起名字而纠结,夸张点可以说是编程5分钟,命名两小时!究竟为什么命名成为了工作中的拦路虎。
+
+每个公司都有不同的标准,目的是为了保持统一,减少沟通成本,提升团队研发效能。所以本文中是笔者结合阿里巴巴开发规范,以及工作中的见闻针对Java领域相关命名进行整理和总结,仅供参考。
+
+## 一,Java中的命名规范
+
+好的命名能体现出代码的特征,含义或者是用途,让阅读者可以根据名称的含义快速厘清程序的脉络。不同语言中采用的命名形式大相径庭,Java中常用到的命名形式共有三种,既首字母大写的UpperCamelCase,首字母小写的lowerCamelCase以及全部大写的并用下划线分割单词的UPPER_CAMEL_UNSER_SCORE。通常约定,**类一般采用大驼峰命名,方法和局部变量使用小驼峰命名,而大写下划线命名通常是常量和枚举中使用。**
+
+| 类型 | 约束 | 例 |
+| :----: | :----------------------------------------------------------: | :--------------------------------------------: |
+| 项目名 | 全部小写,多个单词用中划线分隔‘-’ | spring-cloud |
+| 包名 | 全部小写 | com.alibaba.fastjson |
+| 类名 | 单词首字母大写 | Feature, ParserConfig,DefaultFieldDeserializer |
+| 变量名 | 首字母小写,多个单词组成时,除首个单词,其他单词首字母都要大写 | password, userName |
+| 常量名 | 全部大写,多个单词,用'_'分隔 | CACHE_EXPIRED_TIME |
+| 方法 | 同变量 | read(), readObject(), getById() |
+
+## 二,包命名
+
+**包名**统一使用**小写**,**点分隔符**之间有且仅有一个自然语义的英文单词或者多个单词自然连接到一块(如 springframework,deepspace不需要使用任何分割)。包名统一使用单数形式,如果类命有复数含义,则可以使用复数形式。
+
+包名的构成可以分为以下几四部分【前缀】 【发起者名】【项目名】【模块名】。常见的前缀可以分为以下几种:
+
+| 前缀名 | 例 | 含义 |
+| :-------------: | :----------------------------: | :----------------------------------------------------------: |
+| indi(或onem ) | indi.发起者名.项目名.模块名.…… | 个体项目,指个人发起,但非自己独自完成的项目,可公开或私有项目,copyright主要属于发起者。 |
+| pers | pers.个人名.项目名.模块名.…… | 个人项目,指个人发起,独自完成,可分享的项目,copyright主要属于个人 |
+| priv | priv.个人名.项目名.模块名.…… | 私有项目,指个人发起,独自完成,非公开的私人使用的项目,copyright属于个人。 |
+| team | team.团队名.项目名.模块名.…… | 团队项目,指由团队发起,并由该团队开发的项目,copyright属于该团队所有 |
+| 顶级域名 | com.公司名.项目名.模块名.…… | 公司项目,copyright由项目发起的公司所有 |
+
+## 三,类命名
+
+**类名使用大驼峰命名形式**,类命通常时**名词或名词短语**,接口名除了用名词和名词短语以外,还可以使用形容词或形容词短语,如Cloneable,Callable等,表示实现该接口的类有某种功能或能力。对于测试类则以它要测试的类开头,以Test结尾,如HashMapTest。
+
+对于一些特殊特有名词缩写也可以使用全大写命名,比如XMLHttpRequest,不过笔者认为缩写三个字母以内都大写,超过三个字母则按照要给单词算。这个没有标准如阿里巴巴中fastjson用JSONObject作为类命,而google则使用JsonObjectRequest命名,对于这种特殊的缩写,原则是统一就好。
+
+| 属性 | 约束 | 例 |
+| -------------- | ----------------------------------------- | ------------------------------------------------------------ |
+| 抽象类 | Abstract 或者 Base 开头 | BaseUserService |
+| 枚举类 | Enum 作为后缀 | GenderEnum |
+| 工具类 | Utils作为后缀 | StringUtils |
+| 异常类 | Exception结尾 | RuntimeException |
+| 接口实现类 | 接口名+ Impl | UserServiceImpl |
+| 领域模型相关 | /DO/DTO/VO/DAO | 正例:UserDAO 反例: UserDo, UserDao |
+| 设计模式相关类 | Builder,Factory等 | 当使用到设计模式时,需要使用对应的设计模式作为后缀,如ThreadFactory |
+| 处理特定功能的 | Handler,Predicate, Validator | 表示处理器,校验器,断言,这些类工厂还有配套的方法名如handle,predicate,validate |
+| 测试类 | Test结尾 | UserServiceTest, 表示用来测试UserService类的 |
+| MVC分层 | Controller,Service,ServiceImpl,DAO后缀 | UserManageController,UserManageDAO |
+
+## 四,方法
+
+**方法命名采用小驼峰的形式**,首字小写,往后的每个单词首字母都要大写。 和类名不同的是,方法命名一般为**动词或动词短语**,与参数或参数名共同组成动宾短语,即动词 + 名词。一个好的函数名一般能通过名字直接获知该函数实现什么样的功能。
+
+### 4.1 返回真伪值的方法
+
+注:Prefix-前缀,Suffix-后缀,Alone-单独使用
+
+| 位置 | 单词 | 意义 | 例 |
+| ------ | ------ | ------------------------------------------------------------ | ------------- |
+| Prefix | is | 对象是否符合期待的状态 | isValid |
+| Prefix | can | 对象**能否执行**所期待的动作 | canRemove |
+| Prefix | should | 调用方执行某个命令或方法是**好还是不好**,**应不应该**,或者说**推荐还是不推荐** | shouldMigrate |
+| Prefix | has | 对象**是否持有**所期待的数据和属性 | hasObservers |
+| Prefix | needs | 调用方**是否需要**执行某个命令或方法 | needsMigrate |
+
+### 4.2 用来检查的方法
+
+| 单词 | 意义 | 例 |
+| -------- | ---------------------------------------------------- | -------------- |
+| ensure | 检查是否为期待的状态,不是则抛出异常或返回error code | ensureCapacity |
+| validate | 检查是否为正确的状态,不是则抛出异常或返回error code | validateInputs |
+
+### 4.3 按需求才执行的方法
+
+| 位置 | 单词 | 意义 | 例 |
+| ------ | --------- | ----------------------------------------- | ---------------------- |
+| Suffix | IfNeeded | 需要的时候执行,不需要的时候什么都不做 | drawIfNeeded |
+| Prefix | might | 同上 | mightCreate |
+| Prefix | try | 尝试执行,失败时抛出异常或是返回errorcode | tryCreate |
+| Suffix | OrDefault | 尝试执行,失败时返回默认值 | getOrDefault |
+| Suffix | OrElse | 尝试执行、失败时返回实际参数中指定的值 | getOrElse |
+| Prefix | force | 强制尝试执行。error抛出异常或是返回值 | forceCreate, forceStop |
+
+### 4.4 异步相关方法
+
+| 位置 | 单词 | 意义 | 例 |
+| --------------- | ------------ | -------------------------------------------- | --------------------- |
+| Prefix | blocking | 线程阻塞方法 | blockingGetUser |
+| Suffix | InBackground | 执行在后台的线程 | doInBackground |
+| Suffix | Async | 异步方法 | sendAsync |
+| Suffix | Sync | 对应已有异步方法的同步方法 | sendSync |
+| Prefix or Alone | schedule | Job和Task放入队列 | schedule, scheduleJob |
+| Prefix or Alone | post | 同上 | postJob |
+| Prefix or Alone | execute | 执行异步方法(注:我一般拿这个做同步方法名) | execute, executeTask |
+| Prefix or Alone | start | 同上 | start, startJob |
+| Prefix or Alone | cancel | 停止异步方法 | cancel, cancelJob |
+| Prefix or Alone | stop | 同上 | stop, stopJob |
+
+### 4.5 回调方法
+
+| 位置 | 单词 | 意义 | 例 |
+| ------ | ------ | -------------------------- | ------------ |
+| Prefix | on | 事件发生时执行 | onCompleted |
+| Prefix | before | 事件发生前执行 | beforeUpdate |
+| Prefix | pre | 同上 | preUpdate |
+| Prefix | will | 同上 | willUpdate |
+| Prefix | after | 事件发生后执行 | afterUpdate |
+| Prefix | post | 同上 | postUpdate |
+| Prefix | did | 同上 | didUpdate |
+| Prefix | should | 确认事件是否可以发生时执行 | shouldUpdate |
+
+### 4.6 操作对象生命周期的方法
+
+| 单词 | 意义 | 例 |
+| ---------- | ------------------------------ | --------------- |
+| initialize | 初始化。也可作为延迟初始化使用 | initialize |
+| pause | 暂停 | onPause ,pause |
+| stop | 停止 | onStop,stop |
+| abandon | 销毁的替代 | abandon |
+| destroy | 同上 | destroy |
+| dispose | 同上 | dispose |
+
+### 4.7 与集合操作相关的方法
+
+| 单词 | 意义 | 例 |
+| -------- | ---------------------------- | ---------- |
+| contains | 是否持有与指定对象相同的对象 | contains |
+| add | 添加 | addJob |
+| append | 添加 | appendJob |
+| insert | 插入到下标n | insertJob |
+| put | 添加与key对应的元素 | putJob |
+| remove | 移除元素 | removeJob |
+| enqueue | 添加到队列的最末位 | enqueueJob |
+| dequeue | 从队列中头部取出并移除 | dequeueJob |
+| push | 添加到栈头 | pushJob |
+| pop | 从栈头取出并移除 | popJob |
+| peek | 从栈头取出但不移除 | peekJob |
+| find | 寻找符合条件的某物 | findById |
+
+### 4.8 与数据相关的方法
+
+| 单词 | 意义 | 例 |
+| ------ | -------------------------------------- | ------------- |
+| create | 新创建 | createAccount |
+| new | 新创建 | newAccount |
+| from | 从既有的某物新建,或是从其他的数据新建 | fromConfig |
+| to | 转换 | toString |
+| update | 更新既有某物 | updateAccount |
+| load | 读取 | loadAccount |
+| fetch | 远程读取 | fetchAccount |
+| delete | 删除 | deleteAccount |
+| remove | 删除 | removeAccount |
+| save | 保存 | saveAccount |
+| store | 保存 | storeAccount |
+| commit | 保存 | commitChange |
+| apply | 保存或应用 | applyChange |
+| clear | 清除数据或是恢复到初始状态 | clearAll |
+| reset | 清除数据或是恢复到初始状态 | resetAll |
+
+### 4.9 成对出现的动词
+
+| 单词 | 意义 |
+| -------------- | ----------------- |
+| get获取 | set 设置 |
+| add 增加 | remove 删除 |
+| create 创建 | destory 移除 |
+| start 启动 | stop 停止 |
+| open 打开 | close 关闭 |
+| read 读取 | write 写入 |
+| load 载入 | save 保存 |
+| create 创建 | destroy 销毁 |
+| begin 开始 | end 结束 |
+| backup 备份 | restore 恢复 |
+| import 导入 | export 导出 |
+| split 分割 | merge 合并 |
+| inject 注入 | extract 提取 |
+| attach 附着 | detach 脱离 |
+| bind 绑定 | separate 分离 |
+| view 查看 | browse 浏览 |
+| edit 编辑 | modify 修改 |
+| select 选取 | mark 标记 |
+| copy 复制 | paste 粘贴 |
+| undo 撤销 | redo 重做 |
+| insert 插入 | delete 移除 |
+| add 加入 | append 添加 |
+| clean 清理 | clear 清除 |
+| index 索引 | sort 排序 |
+| find 查找 | search 搜索 |
+| increase 增加 | decrease 减少 |
+| play 播放 | pause 暂停 |
+| launch 启动 | run 运行 |
+| compile 编译 | execute 执行 |
+| debug 调试 | trace 跟踪 |
+| observe 观察 | listen 监听 |
+| build 构建 | publish 发布 |
+| input 输入 | output 输出 |
+| encode 编码 | decode 解码 |
+| encrypt 加密 | decrypt 解密 |
+| compress 压缩 | decompress 解压缩 |
+| pack 打包 | unpack 解包 |
+| parse 解析 | emit 生成 |
+| connect 连接 | disconnect 断开 |
+| send 发送 | receive 接收 |
+| download 下载 | upload 上传 |
+| refresh 刷新 | synchronize 同步 |
+| update 更新 | revert 复原 |
+| lock 锁定 | unlock 解锁 |
+| check out 签出 | check in 签入 |
+| submit 提交 | commit 交付 |
+| push 推 | pull 拉 |
+| expand 展开 | collapse 折叠 |
+| begin 起始 | end 结束 |
+| start 开始 | finish 完成 |
+| enter 进入 | exit 退出 |
+| abort 放弃 | quit 离开 |
+| obsolete 废弃 | depreciate 废旧 |
+| collect 收集 | aggregate 聚集 |
+
+## 五,变量&常量命名
+
+### 5.1 变量命名
+
+变量是指在程序运行中可以改变其值的量,包括成员变量和局部变量。变量名由多单词组成时,第一个单词的首字母小写,其后单词的首字母大写,俗称骆驼式命名法(也称驼峰命名法),如 computedValues,index、变量命名时,尽量简短且能清楚的表达变量的作用,命名体现具体的业务含义即可。
+
+变量名不应以下划线或美元符号开头,尽管这在语法上是允许的。变量名应简短且富于描述。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一次性的临时变量。pojo中的布尔变量,都不要加is(数据库中的布尔字段全都要加 is_ 前缀)。
+
+### 5.2 常量命名
+
+常量命名CONSTANT_CASE,一般采用全部大写(作为方法参数时除外),单词间用下划线分割。那么什么是常量呢?
+
+常量是在作用域内保持不变的值,一般使用final进行修饰。一般分为三种,全局常量(public static final修饰),类内常量(private static final 修饰)以及局部常量(方法内,或者参数中的常量),局部常量比较特殊,通常采用小驼峰命名即可。
+
+```java
+/**
+ * 一个demo
+ *
+ * @author Jann Lee
+ * @date 2019-12-07 00:25
+ **/
+public class HelloWorld {
+
+ /**
+ * 局部常量(正例)
+ */
+ public static final long USER_MESSAGE_CACHE_EXPIRE_TIME = 3600;
+
+ /**
+ * 局部常量(反例,命名不清晰)
+ */
+ public static final long MESSAGE_CACHE_TIME = 3600;
+
+ /**
+ * 全局常量
+ */
+ private static final String ERROR_MESSAGE = " error message";
+
+ /**
+ * 成员变量
+ */
+ private int currentUserId;
+
+ /**
+ * 控制台打印 {@code message} 信息
+ *
+ * @param message 消息体,局部常量
+ */
+ public void sayHello(final String message){
+ System.out.println("Hello world!");
+ }
+
+}
+```
+
+常量一般都有自己的业务含义,**不要害怕长度过长而进行省略或者缩写**。如,用户消息缓存过期时间的表示,那种方式更佳清晰,交给你来评判。
+
+## 通用命名规则[#](https://www.cnblogs.com/liqiangchn/p/12000361.html#450918152)
+
+1. 尽量不要使用拼音;杜绝拼音和英文混用。对于一些通用的表示或者难以用英文描述的可以采用拼音,一旦采用拼音就坚决不能和英文混用。
+ 正例: BeiJing, HangZhou
+ 反例: validateCanShu
+2. 命名过程中尽量不要出现特殊的字符,常量除外。
+3. 尽量不要和jdk或者框架中已存在的类重名,也不能使用java中的关键字命名。
+4. 妙用介词,如for(可以用同音的4代替), to(可用同音的2代替), from, with,of等。
+ 如类名采用User4RedisDO,方法名getUserInfoFromRedis,convertJson2Map等。
+
+## 六,代码注解
+
+### 6.1 注解的原则
+
+好的命名增加代码阅读性,代码的命名往往有严格的限制。而注解不同,程序员往往可以自由发挥,单并不意味着可以为所欲为之胡作非为。优雅的注解通常要满足三要素。
+
+1. Nothing is strange
+ 没有注解的代码对于阅读者非常不友好,哪怕代码写的在清除,阅读者至少从心理上会有抵触,更何况代码中往往有许多复杂的逻辑,所以一定要写注解,不仅要记录代码的逻辑,还有说清楚修改的逻辑。
+2. Less is more
+ 从代码维护角度来讲,代码中的注解一定是精华中的精华。合理清晰的命名能让代码易于理解,对于逻辑简单且命名规范,能够清楚表达代码功能的代码不需要注解。滥用注解会增加额外的负担,更何况大部分都是废话。
+
+```java
+// 根据id获取信息【废话注解】
+getMessageById(id)
+```
+
+1. Advance with the time
+ 注解应该随着代码的变动而改变,注解表达的信息要与代码中完全一致。通常情况下修改代码后一定要修改注解。
+
+### 6.2 注解格式
+
+注解大体上可以分为两种,一种是javadoc注解,另一种是简单注解。javadoc注解可以生成JavaAPI为外部用户提供有效的支持javadoc注解通常在使用IDEA,或者Eclipse等开发工具时都可以自动生成,也支持自定义的注解模板,仅需要对对应的字段进行解释。参与同一项目开发的同学,尽量设置成相同的注解模板。
+
+#### a. 包注解
+
+包注解在工作中往往比较特殊,通过包注解可以快速知悉当前包下代码是用来实现哪些功能,强烈建议工作中加上,尤其是对于一些比较复杂的包,包注解一般在包的根目录下,名称统一为package-info.java。
+
+```java
+/**
+ * 落地也质量检测
+ * 1. 用来解决什么问题
+ * 对广告主投放的广告落地页进行性能检测,模拟不同的系统,如Android,IOS等; 模拟不同的网络:2G,3G,4G,wifi等
+ *
+ * 2. 如何实现
+ * 基于chrome浏览器,用chromedriver驱动浏览器,设置对应的网络,OS参数,获取到浏览器返回结果。
+ *
+ * 注意: 网络环境配置信息{@link cn.mycookies.landingpagecheck.meta.NetWorkSpeedEnum}目前使用是常规速度,可以根据实际情况进行调整
+ *
+ * @author cruder
+ * @time 2019/12/7 20:3 下午
+ */
+package cn.mycookies.landingpagecheck;
+```
+
+#### b. 类注接
+
+javadoc注解中,每个类都必须有注解。
+
+```java
+/**
+* Copyright (C), 2019-2020, Jann balabala...
+*
+* 类的介绍:这是一个用来做什么事情的类,有哪些功能,用到的技术.....
+*
+* @author 类创建者姓名 保持对齐
+* @date 创建日期 保持对齐
+* @version 版本号 保持对齐
+*/
+```
+
+#### c. 属性注解
+
+在每个属性前面必须加上属性注释,通常有以下两种形式,至于怎么选择,你高兴就好,不过一个项目中要保持统一。
+
+```java
+/** 提示信息 */
+private String userName;
+/**
+ * 密码
+ */
+private String password;
+```
+
+#### d. 方法注释
+
+在每个方法前面必须加上方法注释,对于方法中的每个参数,以及返回值都要有说明。
+
+```java
+/**
+ * 方法的详细说明,能干嘛,怎么实现的,注意事项...
+ *
+ * @param xxx 参数1的使用说明, 能否为null
+ * @return 返回结果的说明, 不同情况下会返回怎样的结果
+ * @throws 异常类型 注明从此类方法中抛出异常的说明
+ */
+```
+
+#### e. 构造方法注释
+
+在每个构造方法前面必须加上注释,注释模板如下:
+
+```java
+ /**
+ * 构造方法的详细说明
+ *
+ * @param xxx 参数1的使用说明, 能否为null
+ * @throws 异常类型 注明从此类方法中抛出异常的说明
+ */
+```
+
+而简单注解往往是需要工程师字节定义,在使用注解时应该注意以下几点:
+
+1. 枚举类的各个属性值都要使用注解,枚举可以理解为是常量,通常不会发生改变,通常会被在多个地方引用,对枚举的修改和添加属性通常会带来很大的影响。
+2. 保持排版整洁,不要使用行尾注释;双斜杠和星号之后要用1个空格分隔。
+
+```java
+id = 1;// 反例:不要使用行尾注释
+//反例:换行符与注释之间没有缩进
+int age = 18;
+// 正例:姓名
+String name;
+/**
+ * 1. 多行注释
+ *
+ * 2. 对于不同的逻辑说明,可以用空行分隔
+ */
+```
+
+## 总结
+
+无论是命名和注解,他们的目的都是为了让代码和工程师进行对话,增强代码的可读性,可维护性。优秀的代码往往能够见名知意,注解往往是对命名的补充和完善。命名太南了!
+
+参考文献:
+
+- 《码出高效》
+- https://www.cnblogs.com/wangcp-2014/p/10215620.html
+- https://qiita.com/KeithYokoma/items/2193cf79ba76563e3db6
+- https://google.github.io/styleguide/javaguide.html#s2.1-file-name
\ No newline at end of file
diff --git a/docs/java/java-programming-problem/Java程序设计题.md b/docs/java/java-programming-problem/Java程序设计题.md
new file mode 100644
index 00000000..112c1bff
--- /dev/null
+++ b/docs/java/java-programming-problem/Java程序设计题.md
@@ -0,0 +1,130 @@
+
+
+- [0.0.1. 泛型的实际应用:实现最小值函数](#001-%e6%b3%9b%e5%9e%8b%e7%9a%84%e5%ae%9e%e9%99%85%e5%ba%94%e7%94%a8%e5%ae%9e%e7%8e%b0%e6%9c%80%e5%b0%8f%e5%80%bc%e5%87%bd%e6%95%b0)
+- [0.0.2. 使用数组实现栈](#002-%e4%bd%bf%e7%94%a8%e6%95%b0%e7%bb%84%e5%ae%9e%e7%8e%b0%e6%a0%88)
+- [0.0.3. 实现线程安全的 LRU 缓存](#003-%e5%ae%9e%e7%8e%b0%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e7%9a%84-lru-%e7%bc%93%e5%ad%98)
+
+
+
+### 0.0.1. 泛型的实际应用:实现最小值函数
+
+自己设计一个泛型的获取数组最小值的函数.并且这个方法只能接受Number的子类并且实现了Comparable接口。
+
+```java
+//注意:Number并没有实现Comparable
+private static > T min(T[] values) {
+ if (values == null || values.length == 0) return null;
+ T min = values[0];
+ for (int i = 1; i < values.length; i++) {
+ if (min.compareTo(values[i]) > 0) min = values[i];
+ }
+ return min;
+}
+```
+
+测试:
+
+```java
+int minInteger = min(new Integer[]{1, 2, 3});//result:1
+double minDouble = min(new Double[]{1.2, 2.2, -1d});//result:-1d
+String typeError = min(new String[]{"1","3"});//报错
+```
+### 0.0.2. 使用数组实现栈
+
+**自己实现一个栈,要求这个栈具有`push()`、`pop()`(返回栈顶元素并出栈)、`peek()` (返回栈顶元素不出栈)、`isEmpty()`、`size()`这些基本的方法。**
+
+提示:每次入栈之前先判断栈的容量是否够用,如果不够用就用`Arrays.copyOf()`进行扩容;
+
+```java
+public class MyStack {
+ private int[] storage;//存放栈中元素的数组
+ private int capacity;//栈的容量
+ private int count;//栈中元素数量
+ private static final int GROW_FACTOR = 2;
+
+ //不带初始容量的构造方法。默认容量为8
+ public MyStack() {
+ this.capacity = 8;
+ this.storage=new int[8];
+ this.count = 0;
+ }
+
+ //带初始容量的构造方法
+ public MyStack(int initialCapacity) {
+ if (initialCapacity < 1)
+ throw new IllegalArgumentException("Capacity too small.");
+
+ this.capacity = initialCapacity;
+ this.storage = new int[initialCapacity];
+ this.count = 0;
+ }
+
+ //入栈
+ public void push(int value) {
+ if (count == capacity) {
+ ensureCapacity();
+ }
+ storage[count++] = value;
+ }
+
+ //确保容量大小
+ private void ensureCapacity() {
+ int newCapacity = capacity * GROW_FACTOR;
+ storage = Arrays.copyOf(storage, newCapacity);
+ capacity = newCapacity;
+ }
+
+ //返回栈顶元素并出栈
+ private int pop() {
+ if (count == 0)
+ throw new IllegalArgumentException("Stack is empty.");
+ count--;
+ return storage[count];
+ }
+
+ //返回栈顶元素不出栈
+ private int peek() {
+ if (count == 0){
+ throw new IllegalArgumentException("Stack is empty.");
+ }else {
+ return storage[count-1];
+ }
+ }
+
+ //判断栈是否为空
+ private boolean isEmpty() {
+ return count == 0;
+ }
+
+ //返回栈中元素的个数
+ private int size() {
+ return count;
+ }
+
+}
+
+```
+
+验证
+
+```java
+MyStack myStack = new MyStack(3);
+myStack.push(1);
+myStack.push(2);
+myStack.push(3);
+myStack.push(4);
+myStack.push(5);
+myStack.push(6);
+myStack.push(7);
+myStack.push(8);
+System.out.println(myStack.peek());//8
+System.out.println(myStack.size());//8
+for (int i = 0; i < 8; i++) {
+ System.out.println(myStack.pop());
+}
+System.out.println(myStack.isEmpty());//true
+myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
+```
+
+
+
diff --git a/docs/java/java-programming-problem/a-thread-safe-implementation-of-lru-cache.md b/docs/java/java-programming-problem/a-thread-safe-implementation-of-lru-cache.md
new file mode 100644
index 00000000..7c367e11
--- /dev/null
+++ b/docs/java/java-programming-problem/a-thread-safe-implementation-of-lru-cache.md
@@ -0,0 +1,441 @@
+
+
+- [1. LRU 缓存介绍](#1-lru-%e7%bc%93%e5%ad%98%e4%bb%8b%e7%bb%8d)
+- [2. ConcurrentLinkedQueue简单介绍](#2-concurrentlinkedqueue%e7%ae%80%e5%8d%95%e4%bb%8b%e7%bb%8d)
+- [3. ReadWriteLock简单介绍](#3-readwritelock%e7%ae%80%e5%8d%95%e4%bb%8b%e7%bb%8d)
+- [4. ScheduledExecutorService 简单介绍](#4-scheduledexecutorservice-%e7%ae%80%e5%8d%95%e4%bb%8b%e7%bb%8d)
+- [5. 徒手撸一个线程安全的 LRU 缓存](#5-%e5%be%92%e6%89%8b%e6%92%b8%e4%b8%80%e4%b8%aa%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e7%9a%84-lru-%e7%bc%93%e5%ad%98)
+ - [5.1. 实现方法](#51-%e5%ae%9e%e7%8e%b0%e6%96%b9%e6%b3%95)
+ - [5.2. 原理](#52-%e5%8e%9f%e7%90%86)
+ - [5.3. put方法具体流程分析](#53-put%e6%96%b9%e6%b3%95%e5%85%b7%e4%bd%93%e6%b5%81%e7%a8%8b%e5%88%86%e6%9e%90)
+ - [5.4. 源码](#54-%e6%ba%90%e7%a0%81)
+- [6. 实现一个线程安全并且带有过期时间的 LRU 缓存](#6-%e5%ae%9e%e7%8e%b0%e4%b8%80%e4%b8%aa%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e5%b9%b6%e4%b8%94%e5%b8%a6%e6%9c%89%e8%bf%87%e6%9c%9f%e6%97%b6%e9%97%b4%e7%9a%84-lru-%e7%bc%93%e5%ad%98)
+
+
+
+最近被读者问到“不用LinkedHashMap的话,如何实现一个线程安全的 LRU 缓存?网上的代码太杂太乱,Guide哥哥能不能帮忙写一个?”。
+
+*划重点,手写一个 LRU 缓存在面试中还是挺常见的!*
+
+很多人就会问了:“网上已经有这么多现成的缓存了!为什么面试官还要我们自己实现一个呢?” 。咳咳咳,当然是为了面试需要。哈哈!开个玩笑,我个人觉得更多地是为了学习吧!今天Guide哥教大家:
+
+1. 实现一个线程安全的 LRU 缓存
+2. 实现一个线程安全并且带有过期时间的 LRU 缓存
+
+考虑到了线程安全性我们使用了 `ConcurrentHashMap` 、`ConcurrentLinkedQueue` 这两个线程安全的集合。另外,还用到 `ReadWriteLock`(读写锁)。为了实现带有过期时间的缓存,我们用到了 `ScheduledExecutorService`来做定时任务执行。
+
+如果有任何不对或者需要完善的地方,请帮忙指出!
+
+### 1. LRU 缓存介绍
+
+**LRU (Least Recently Used,最近最少使用)是一种缓存淘汰策略。**
+
+LRU缓存指的是当缓存大小已达到最大分配容量的时候,如果再要去缓存新的对象数据的话,就需要将缓存中最近访问最少的对象删除掉以便给新来的数据腾出空间。
+
+### 2. ConcurrentLinkedQueue简单介绍
+
+**ConcurrentLinkedQueue是一个基于单向链表的无界无锁线程安全的队列,适合在高并发环境下使用,效率比较高。** 我们在使用的时候,可以就把它理解为我们经常接触的数据结构——队列,不过是增加了多线程下的安全性保证罢了。**和普通队列一样,它也是按照先进先出(FIFO)的规则对接点进行排序。** 另外,队列元素中不可以放置null元素。
+
+`ConcurrentLinkedQueue` 整个继承关系如下图所示:
+
+
+
+`ConcurrentLinkedQueue中`最主要的两个方法是:`offer(value)`和`poll()`,分别实现队列的两个重要的操作:入队和出队(`offer(value)`等价于 `add(value)`)。
+
+我们添加一个元素到队列的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。
+
+
+
+利用`ConcurrentLinkedQueue`队列先进先出的特性,每当我们 `put`/`get`(缓存被使用)元素的时候,我们就将这个元素存放在队列尾部,这样就能保证队列头部的元素是最近最少使用的。
+
+### 3. ReadWriteLock简单介绍
+
+`ReadWriteLock` 是一个接口,位于`java.util.concurrent.locks`包下,里面只有两个方法分别返回读锁和写锁:
+
+```java
+public interface ReadWriteLock {
+ /**
+ * 返回读锁
+ */
+ Lock readLock();
+
+ /**
+ * 返回写锁
+ */
+ Lock writeLock();
+}
+```
+
+`ReentrantReadWriteLock` 是`ReadWriteLock`接口的具体实现类。
+
+**读写锁还是比较适合缓存这种读多写少的场景。读写锁可以保证多个线程和同时读取,但是只有一个线程可以写入。**
+
+读写锁的特点是:写锁和写锁互斥,读锁和写锁互斥,读锁之间不互斥。也就说:同一时刻只能有一个线程写,但是可以有多个线程
+读。读写之间是互斥的,两者不能同时发生(当进行写操作时,同一时刻其他线程的读操作会被阻塞;当进行读操作时,同一时刻所有线程的写操作会被阻塞)。
+
+另外,**同一个线程持有写锁时是可以申请读锁,但是持有读锁的情况下不可以申请写锁。**
+
+### 4. ScheduledExecutorService 简单介绍
+
+`ScheduledExecutorService` 是一个接口,`ScheduledThreadPoolExecutor` 是其主要实现类。
+
+
+
+**`ScheduledThreadPoolExecutor` 主要用来在给定的延迟后运行任务,或者定期执行任务。** 这个在实际项目用到的比较少,因为有其他方案选择比如`quartz`。但是,在一些需求比较简单的场景下还是非常有用的!
+
+**`ScheduledThreadPoolExecutor` 使用的任务队列 `DelayQueue` 封装了一个 `PriorityQueue`,`PriorityQueue` 会对队列中的任务进行排序,执行所需时间短的放在前面先被执行,如果执行所需时间相同则先提交的任务将被先执行。**
+
+### 5. 徒手撸一个线程安全的 LRU 缓存
+
+#### 5.1. 实现方法
+
+ `ConcurrentHashMap` + `ConcurrentLinkedQueue` +`ReadWriteLock`
+
+#### 5.2. 原理
+
+`ConcurrentHashMap` 是线程安全的Map,我们可以利用它缓存 key,value形式的数据。`ConcurrentLinkedQueue`是一个线程安全的基于链表的队列(先进先出),我们可以用它来维护 key 。每当我们put/get(缓存被使用)元素的时候,我们就将这个元素对应的 key 存放在队列尾部,这样就能保证队列头部的元素是最近最少使用的。当我们的缓存容量不够的时候,我们直接移除队列头部对应的key以及这个key对应的缓存即可!
+
+另外,我们用到了`ReadWriteLock`(读写锁)来保证线程安全。
+
+#### 5.3. put方法具体流程分析
+
+为了方便大家理解,我将代码中比较重要的 `put(key,value)`方法的原理图画了出来,如下图所示:
+
+
+
+
+
+#### 5.4. 源码
+
+```java
+/**
+ * @author shuang.kou
+ *
+ * 使用 ConcurrentHashMap+ConcurrentLinkedQueue+ReadWriteLock实现线程安全的 LRU 缓存
+ * 这里只是为了学习使用,本地缓存推荐使用 Guava 自带的,使用 Spring 的话,推荐使用Spring Cache
+ */
+public class MyLruCache {
+
+ /**
+ * 缓存的最大容量
+ */
+ private final int maxCapacity;
+
+ private ConcurrentHashMap cacheMap;
+ private ConcurrentLinkedQueue keys;
+ /**
+ * 读写锁
+ */
+ private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+ private Lock writeLock = readWriteLock.writeLock();
+ private Lock readLock = readWriteLock.readLock();
+
+ public MyLruCache(int maxCapacity) {
+ if (maxCapacity < 0) {
+ throw new IllegalArgumentException("Illegal max capacity: " + maxCapacity);
+ }
+ this.maxCapacity = maxCapacity;
+ cacheMap = new ConcurrentHashMap<>(maxCapacity);
+ keys = new ConcurrentLinkedQueue<>();
+ }
+
+ public V put(K key, V value) {
+ // 加写锁
+ writeLock.lock();
+ try {
+ //1.key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ moveToTailOfQueue(key);
+ cacheMap.put(key, value);
+ return value;
+ }
+ //2.是否超出缓存容量,超出的话就移除队列头部的元素以及其对应的缓存
+ if (cacheMap.size() == maxCapacity) {
+ System.out.println("maxCapacity of cache reached");
+ removeOldestKey();
+ }
+ //3.key不存在于当前缓存。将key添加到队列的尾部并且缓存key及其对应的元素
+ keys.add(key);
+ cacheMap.put(key, value);
+ return value;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public V get(K key) {
+ //加读锁
+ readLock.lock();
+ try {
+ //key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ // 存在的话就将key移动到队列的尾部
+ moveToTailOfQueue(key);
+ return cacheMap.get(key);
+ }
+ //不存在于当前缓存中就返回Null
+ return null;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public V remove(K key) {
+ writeLock.lock();
+ try {
+ //key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ // 存在移除队列和Map中对应的Key
+ keys.remove(key);
+ return cacheMap.remove(key);
+ }
+ //不存在于当前缓存中就返回Null
+ return null;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * 将元素添加到队列的尾部(put/get的时候执行)
+ */
+ private void moveToTailOfQueue(K key) {
+ keys.remove(key);
+ keys.add(key);
+ }
+
+ /**
+ * 移除队列头部的元素以及其对应的缓存 (缓存容量已满的时候执行)
+ */
+ private void removeOldestKey() {
+ K oldestKey = keys.poll();
+ if (oldestKey != null) {
+ cacheMap.remove(oldestKey);
+ }
+ }
+
+ public int size() {
+ return cacheMap.size();
+ }
+
+}
+```
+
+**非并发环境测试:**
+
+```java
+MyLruCache myLruCache = new MyLruCache<>(3);
+myLruCache.put(1, "Java");
+System.out.println(myLruCache.get(1));// Java
+myLruCache.remove(1);
+System.out.println(myLruCache.get(1));// null
+myLruCache.put(2, "C++");
+myLruCache.put(3, "Python");
+System.out.println(myLruCache.get(2));//C++
+myLruCache.put(4, "C");
+myLruCache.put(5, "PHP");
+System.out.println(myLruCache.get(2));// C++
+```
+
+**并发环境测试:**
+
+我们初始化了一个固定容量为 10 的线程池和count为10的`CountDownLatch`。我们将1000000次操作分10次添加到线程池,然后我们等待线程池执行完成这10次操作。
+
+
+```java
+int threadNum = 10;
+int batchSize = 100000;
+//init cache
+MyLruCache myLruCache = new MyLruCache<>(batchSize * 10);
+//init thread pool with 10 threads
+ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadNum);
+//init CountDownLatch with 10 count
+CountDownLatch latch = new CountDownLatch(threadNum);
+AtomicInteger atomicInteger = new AtomicInteger(0);
+long startTime = System.currentTimeMillis();
+for (int t = 0; t < threadNum; t++) {
+ fixedThreadPool.submit(() -> {
+ for (int i = 0; i < batchSize; i++) {
+ int value = atomicInteger.incrementAndGet();
+ myLruCache.put("id" + value, value);
+ }
+ latch.countDown();
+ });
+}
+//wait for 10 threads to complete the task
+latch.await();
+fixedThreadPool.shutdown();
+System.out.println("Cache size:" + myLruCache.size());//Cache size:1000000
+long endTime = System.currentTimeMillis();
+long duration = endTime - startTime;
+System.out.println(String.format("Time cost:%dms", duration));//Time cost:511ms
+```
+
+### 6. 实现一个线程安全并且带有过期时间的 LRU 缓存
+
+实际上就是在我们上面时间的LRU缓存的基础上加上一个定时任务去删除缓存,单纯利用 JDK 提供的类,我们实现定时任务的方式有很多种:
+
+1. `Timer` :不被推荐,多线程会存在问题。
+2. `ScheduledExecutorService` :定时器线程池,可以用来替代 `Timer`
+3. `DelayQueue` :延时队列
+4. `quartz` :一个很火的开源任务调度框架,很多其他框架都是基于 `quartz` 开发的,比如当当网的`elastic-job `就是基于`quartz`二次开发之后的分布式调度解决方案
+5. ......
+
+最终我们选择了 `ScheduledExecutorService`,主要原因是它易用(基于`DelayQueue`做了很多封装)并且基本能满足我们的大部分需求。
+
+我们在我们上面实现的线程安全的 LRU 缓存基础上,简单稍作修改即可!我们增加了一个方法:
+
+```java
+private void removeAfterExpireTime(K key, long expireTime) {
+ scheduledExecutorService.schedule(() -> {
+ //过期后清除该键值对
+ cacheMap.remove(key);
+ keys.remove(key);
+ }, expireTime, TimeUnit.MILLISECONDS);
+}
+```
+我们put元素的时候,如果通过这个方法就能直接设置过期时间。
+
+
+**完整源码如下:**
+
+```java
+/**
+ * @author shuang.kou
+ *
+ * 使用 ConcurrentHashMap+ConcurrentLinkedQueue+ReadWriteLock+ScheduledExecutorService实现线程安全的 LRU 缓存
+ * 这里只是为了学习使用,本地缓存推荐使用 Guava 自带的,使用 Spring 的话,推荐使用Spring Cache
+ */
+public class MyLruCacheWithExpireTime {
+
+ /**
+ * 缓存的最大容量
+ */
+ private final int maxCapacity;
+
+ private ConcurrentHashMap cacheMap;
+ private ConcurrentLinkedQueue keys;
+ /**
+ * 读写锁
+ */
+ private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+ private Lock writeLock = readWriteLock.writeLock();
+ private Lock readLock = readWriteLock.readLock();
+
+ private ScheduledExecutorService scheduledExecutorService;
+
+ public MyLruCacheWithExpireTime(int maxCapacity) {
+ if (maxCapacity < 0) {
+ throw new IllegalArgumentException("Illegal max capacity: " + maxCapacity);
+ }
+ this.maxCapacity = maxCapacity;
+ cacheMap = new ConcurrentHashMap<>(maxCapacity);
+ keys = new ConcurrentLinkedQueue<>();
+ scheduledExecutorService = Executors.newScheduledThreadPool(3);
+ }
+
+ public V put(K key, V value, long expireTime) {
+ // 加写锁
+ writeLock.lock();
+ try {
+ //1.key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ moveToTailOfQueue(key);
+ cacheMap.put(key, value);
+ return value;
+ }
+ //2.是否超出缓存容量,超出的话就移除队列头部的元素以及其对应的缓存
+ if (cacheMap.size() == maxCapacity) {
+ System.out.println("maxCapacity of cache reached");
+ removeOldestKey();
+ }
+ //3.key不存在于当前缓存。将key添加到队列的尾部并且缓存key及其对应的元素
+ keys.add(key);
+ cacheMap.put(key, value);
+ if (expireTime > 0) {
+ removeAfterExpireTime(key, expireTime);
+ }
+ return value;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ public V get(K key) {
+ //加读锁
+ readLock.lock();
+ try {
+ //key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ // 存在的话就将key移动到队列的尾部
+ moveToTailOfQueue(key);
+ return cacheMap.get(key);
+ }
+ //不存在于当前缓存中就返回Null
+ return null;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ public V remove(K key) {
+ writeLock.lock();
+ try {
+ //key是否存在于当前缓存
+ if (cacheMap.containsKey(key)) {
+ // 存在移除队列和Map中对应的Key
+ keys.remove(key);
+ return cacheMap.remove(key);
+ }
+ //不存在于当前缓存中就返回Null
+ return null;
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * 将元素添加到队列的尾部(put/get的时候执行)
+ */
+ private void moveToTailOfQueue(K key) {
+ keys.remove(key);
+ keys.add(key);
+ }
+
+ /**
+ * 移除队列头部的元素以及其对应的缓存 (缓存容量已满的时候执行)
+ */
+ private void removeOldestKey() {
+ K oldestKey = keys.poll();
+ if (oldestKey != null) {
+ cacheMap.remove(oldestKey);
+ }
+ }
+
+ private void removeAfterExpireTime(K key, long expireTime) {
+ scheduledExecutorService.schedule(() -> {
+ //过期后清除该键值对
+ cacheMap.remove(key);
+ keys.remove(key);
+ }, expireTime, TimeUnit.MILLISECONDS);
+ }
+
+ public int size() {
+ return cacheMap.size();
+ }
+
+}
+
+```
+
+**测试效果:**
+
+```java
+MyLruCacheWithExpireTime myLruCache = new MyLruCacheWithExpireTime<>(3);
+myLruCache.put(1,"Java",3000);
+myLruCache.put(2,"C++",3000);
+myLruCache.put(3,"Python",1500);
+System.out.println(myLruCache.size());//3
+Thread.sleep(2000);
+System.out.println(myLruCache.size());//2
+```
diff --git a/docs/java/jdk-new-features/new-features-from-jdk8-to-jdk14.md b/docs/java/jdk-new-features/new-features-from-jdk8-to-jdk14.md
new file mode 100644
index 00000000..4fabcf3c
--- /dev/null
+++ b/docs/java/jdk-new-features/new-features-from-jdk8-to-jdk14.md
@@ -0,0 +1,441 @@
+大家好,我是Guide哥!这篇文章来自读者的投稿,经过了两次较大的改动,两周的完善终于完成。Java 8新特性见这里:[Java8新特性最佳指南](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484744&idx=1&sn=9db31dca13d327678845054af75efb74&chksm=cea24a83f9d5c3956f4feb9956b068624ab2fdd6c4a75fe52d5df5dca356a016577301399548&token=1082669959&lang=zh_CN&scene=21#wechat_redirect) 。
+
+*Guide 哥:别人家的特性都用了几年了,我 Java 才出来,哈哈!真实!*
+
+## Java9
+
+发布于 2017 年 9 月 21 日 。作为 Java8 之后 3 年半才发布的新版本,Java 9 带 来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如集合、Stream 流
+
+### Java 平台模块系统
+
+Java 平台模块系统,也就是 Project Jigsaw,把模块化开发实践引入到了 Java 平台中。在引入了模块系统之后,JDK 被重新组织成 94 个模块。Java 应用可以通过新增的 jlink 工具,创建出只包含所依赖的 JDK 模块的自定义运行时镜像。这样可以极大的减少 Java 运行时环境的大小。
+
+Java 9 模块的重要特征是在其工件(artifact)的根目录中包含了一个描述模块的 module-info.class 文 件。 工件的格式可以是传统的 JAR 文件或是 Java 9 新增的 JMOD 文件。
+
+### Jshell
+
+jshell 是 Java 9 新增的一个实用工具。为 Java 提供了类似于 Python 的实时命令行交互工具。
+
+在 Jshell 中可以直接输入表达式并查看其执行结果
+
+### 集合、Stream 和 Optional
+
+- 增加 了 `List.of()`、`Set.of()`、`Map.of()` 和 `Map.ofEntries()`等工厂方法来创建不可变集合,比如`List.of("Java", "C++");`、`Map.of("Java", 1, "C++", 2)`;(这部分内容有点参考 Guava 的味道)
+- `Stream` 中增加了新的方法 `ofNullable`、`dropWhile`、`takeWhile` 和 `iterate` 方法。`Collectors` 中增加了新的方法 `filtering` 和 `flatMapping`
+- `Optional` 类中新增了 `ifPresentOrElse`、`or` 和 `stream` 等方法
+
+### 进程 API
+
+Java 9 增加了 `ProcessHandle` 接口,可以对原生进程进行管理,尤其适合于管理长时间运行的进程
+
+### 平台日志 API 和服务
+
+Java 9 允许为 JDK 和应用配置同样的日志实现。新增了 `System.LoggerFinder` 用来管理 JDK 使 用的日志记录器实现。JVM 在运行时只有一个系统范围的 `LoggerFinder` 实例。
+
+我们可以通过添加自己的 `System.LoggerFinder` 实现来让 JDK 和应用使用 SLF4J 等其他日志记录框架。
+
+### 反应式流 ( Reactive Streams )
+
+- 在 Java9 中的 `java.util.concurrent.Flow` 类中新增了反应式流规范的核心接口
+- Flow 中包含了 `Flow.Publisher`、`Flow.Subscriber`、`Flow.Subscription` 和 `Flow.Processor` 等 4 个核心接口。Java 9 还提供了`SubmissionPublisher` 作为`Flow.Publisher` 的一个实现。
+
+### 变量句柄
+
+- 变量句柄是一个变量或一组变量的引用,包括静态域,非静态域,数组元素和堆外数据结构中的组成部分等
+- 变量句柄的含义类似于已有的方法句柄`MethodHandle`
+- 由 Java 类`java.lang.invoke.VarHandle` 来表示,可以使用类 `java.lang.invoke.MethodHandles.Lookup` 中的静态工厂方法来创建 `VarHandle` 对 象
+
+### 改进方法句柄(Method Handle)
+
+- 方法句柄从 Java7 开始引入,Java9 在类`java.lang.invoke.MethodHandles` 中新增了更多的静态方法来创建不同类型的方法句柄
+
+### 其它新特性
+
+- **接口私有方法** :Java 9 允许在接口中使用私有方法
+- **try-with-resources 增强** :在 try-with-resources 语句中可以使用 effectively-final 变量(什么是 effectively-final 变量,见这篇文章 [http://ilkinulas.github.io/programming/java/2016/03/27/effectively-final-java.html](http://ilkinulas.github.io/programming/java/2016/03/27/effectively-final-java.html))
+- **类 `CompletableFuture` 中增加了几个新的方法(`completeAsync` ,`orTimeout` 等)**
+- **Nashorn 引擎的增强** :Nashorn 从 Java8 开始引入的 JavaScript 引擎,Java9 对 Nashorn 做了些增强,实现了一些 ES6 的新特性
+- **I/O 流的新特性** :增加了新的方法来读取和复制 InputStream 中包含的数据
+- **改进应用的安全性能** :Java 9 新增了 4 个 SHA- 3 哈希算法,SHA3-224、SHA3-256、SHA3-384 和 S HA3-512
+- ......
+
+## Java10
+
+发布于 2018 年 3 月 20 日,最知名的特性应该是 var 关键字(局部变量类型推断)的引入了,其他还有垃圾收集器改善、GC 改进、性能提升、线程管控等一批新特性
+
+### var 关键字
+
+- **介绍** :提供了 var 关键字声明局部变量:`var list = new ArrayList(); // ArrayList`
+- **局限性** :只能用于带有构造器的**局部变量**和 for 循环中
+
+_Guide 哥:实际上 Lombok 早就体用了一个类似的关键字,使用它可以简化代码,但是可能会降低程序的易读性、可维护性。一般情况下,我个人都不太推荐使用。_
+
+### 不可变集合
+
+**list,set,map 提供了静态方法`copyOf()`返回入参集合的一个不可变拷贝(以下为 JDK 的源码)**
+
+```java
+static List copyOf(Collection extends E> coll) {
+ return ImmutableCollections.listCopy(coll);
+}
+```
+
+**`java.util.stream.Collectors`中新增了静态方法,用于将流中的元素收集为不可变的集合**
+
+### Optional
+
+- 新增了`orElseThrow()`方法来在没有值时抛出异常
+
+### 并行全垃圾回收器 G1
+
+从 Java9 开始 G1 就了默认的垃圾回收器,G1 是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是 Java9 的 G1 的 FullGC 依然是使用单线程去完成标记清除算法,这可能会导致垃圾回收期在无法回收内存的时候触发 Full GC。
+
+为了最大限度地减少 Full GC 造成的应用停顿的影响,从 Java10 开始,G1 的 FullGC 改为并行的标记清除算法,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
+
+### 应用程序类数据共享
+
+在 Java 5 中就已经引入了类数据共享机制 (Class Data Sharing,简称 CDS),允许将一组类预处理为共享归档文件,以便在运行时能够进行内存映射以减少 Java 程序的启动时间,当多个 Java 虚拟机(JVM)共享相同的归档文件时,还可以减少动态内存的占用量,同时减少多个虚拟机在同一个物理或虚拟的机器上运行时的资源占用
+
+Java 10 在现有的 CDS 功能基础上再次拓展,以允许应用类放置在共享存档中。CDS 特性在原来的 bootstrap 类基础之上,扩展加入了应用类的 CDS (Application Class-Data Sharing) 支持。其原理为:在启动时记录加载类的过程,写入到文本文件中,再次启动时直接读取此启动文本并加载。设想如果应用环境没有大的变化,启动速度就会得到提升
+
+### 其他特性
+
+- **线程-局部管控**:Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程
+
+- **备用存储装置上的堆分配**:Java 10 中将使得 JVM 能够使用适用于不同类型的存储机制的堆,在可选内存设备上进行堆内存分配
+- **统一的垃圾回收接口**:Java 10 中,hotspot/gc 代码实现方面,引入一个干净的 GC 接口,改进不同 GC 源代码的隔离性,多个 GC 之间共享的实现细节代码应该存在于辅助类中。统一垃圾回收接口的主要原因是:让垃圾回收器(GC)这部分代码更加整洁,便于新人上手开发,便于后续排查相关问题。
+
+## Java11
+
+Java11 于 2018 年 9 月 25 日正式发布,这是很重要的一个版本!Java 11 和 2017 年 9 月份发布的 Java 9 以及 2018 年 3 月份发布的 Java 10 相比,其最大的区别就是:在长期支持(Long-Term-Support)方面,**Oracle 表示会对 Java 11 提供大力支持,这一支持将会持续至 2026 年 9 月。这是据 Java 8 以后支持的首个长期版本。**
+
+
+
+### 字符串加强
+
+Java 11 增加了一系列的字符串处理方法,如以下所示。
+
+_Guide 哥:说白点就是多了层封装,JDK 开发组的人没少看市面上常见的工具类框架啊!_
+
+```java
+//判断字符串是否为空
+" ".isBlank();//true
+//去除字符串首尾空格
+" Java ".strip();// "Java"
+//去除字符串首部空格
+" Java ".stripLeading(); // "Java "
+//去除字符串尾部空格
+" Java ".stripTrailing(); // " Java"
+//重复字符串多少次
+"Java".repeat(3); // "JavaJavaJava"
+
+//返回由行终止符分隔的字符串集合。
+"A\nB\nC".lines().count(); // 3
+"A\nB\nC".lines().collect(Collectors.toList());
+```
+
+### ZGC:可伸缩低延迟垃圾收集器
+
+**ZGC 即 Z Garbage Collector**,是一个可伸缩的、低延迟的垃圾收集器。
+
+ZGC 主要为了满足如下目标进行设计:
+
+- GC 停顿时间不超过 10ms
+- 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
+- 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
+- 方便在此基础上引入新的 GC 特性和利用 colord
+- 针以及 Load barriers 优化奠定基础
+- 当前只支持 Linux/x64 位平台
+
+ZGC 目前 **处在实验阶段**,只支持 Linux/x64 平台
+
+### 标准 HTTP Client 升级
+
+Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 Http Client API 进行了标准化,在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。
+
+并且,Java11 中,Http Client 的包名由 `jdk.incubator.http` 改为`java.net.http`,该 API 通过 `CompleteableFuture` 提供非阻塞请求和响应语义。
+
+使用起来也很简单,如下:
+
+```java
+var request = HttpRequest.newBuilder()
+
+ .uri(URI.create("https://javastack.cn"))
+
+ .GET()
+
+ .build();
+
+var client = HttpClient.newHttpClient();
+
+// 同步
+
+HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+System.out.println(response.body());
+
+// 异步
+
+client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
+
+ .thenApply(HttpResponse::body)
+
+ .thenAccept(System.out::println);
+
+
+```
+
+### 简化启动单个源代码文件的方法
+
+- 增强了 Java 启动器,使其能够运行单一文件的 Java 源代码。此功能允许使用 Java 解释器直接执行 Java 源代码。源代码在内存中编译,然后由解释器执行。唯一的约束在于所有相关的类必须定义在同一个 Java 文件中
+- 对于 Java 初学者并希望尝试简单程序的人特别有用,并且能和 jshell 一起使用
+- 一定能程度上增强了使用 Java 来写脚本程序的能力
+
+### 用于 Lambda 参数的局部变量语法
+
+- 从 Java 10 开始,便引入了局部变量类型推断这一关键特性。类型推断允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推断出类型
+- Java 10 中对 var 关键字存在几个限制
+ - 只能用于局部变量上
+ - 声明时必须初始化
+ - 不能用作方法参数
+ - 不能在 Lambda 表达式中使用
+- Java11 开始允许开发者在 Lambda 表达式中使用 var 进行参数声明
+
+### 其他特性
+
+- 新的垃圾回收器 Epsilon,一个完全消极的 GC 实现,分配有限的内存资源,最大限度的降低内存占用和内存吞吐延迟时间
+- 低开销的 Heap Profiling:Java 11 中提供一种低开销的 Java 堆分配采样方法,能够得到堆分配的 Java 对象信息,并且能够通过 JVMTI 访问堆信息
+- TLS1.3 协议:Java 11 中包含了传输层安全性(TLS)1.3 规范(RFC 8446)的实现,替换了之前版本中包含的 TLS,包括 TLS 1.2,同时还改进了其他 TLS 功能,例如 OCSP 装订扩展(RFC 6066,RFC 6961),以及会话散列和扩展主密钥扩展(RFC 7627),在安全性和性能方面也做了很多提升
+- 飞行记录器:飞行记录器之前是商业版 JDK 的一项分析工具,但在 Java 11 中,其代码被包含到公开代码库中,这样所有人都能使用该功能了
+
+## Java12
+
+### 增强 Switch
+
+- 传统的 switch 语法存在容易漏写 break 的问题,而且从代码整洁性层面来看,多个 break 本质也是一种重复
+
+- Java12 提供了 swtich 表达式,使用类似 lambda 语法条件匹配成功后的执行块,不需要多写 break
+
+- 作为预览特性加入,需要在`javac`编译和`java`运行时增加参数`--enable-preview`
+
+ ```java
+ switch (day) {
+ case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
+ case TUESDAY -> System.out.println(7);
+ case THURSDAY, SATURDAY -> System.out.println(8);
+ case WEDNESDAY -> System.out.println(9);
+ }
+ ```
+
+### 数字格式化工具类
+
+- `NumberFormat` 新增了对复杂的数字进行格式化的支持
+
+ ```java
+ NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
+ String result = fmt.format(1000);
+ System.out.println(result); // 输出为 1K,计算工资是多少K更方便了。。。
+ ```
+
+### Shenandoah GC
+
+- Redhat 主导开发的 Pauseless GC 实现,主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等
+- 和 Java11 开源的 ZGC 相比(需要升级到 JDK11 才能使用),Shenandoah GC 有稳定的 JDK8u 版本,在 Java8 占据主要市场份额的今天有更大的可落地性
+
+### G1 收集器提升
+
+- **Java12 为默认的垃圾收集器 G1 带来了两项更新:**
+ - 可中止的混合收集集合:JEP344 的实现,为了达到用户提供的停顿时间目标,JEP 344 通过把要被回收的区域集(混合收集集合)拆分为强制和可选部分,使 G1 垃圾回收器能中止垃圾回收过程。 G1 可以中止可选部分的回收以达到停顿时间目标
+ - 及时返回未使用的已分配内存:JEP346 的实现,增强 G1 GC,以便在空闲时自动将 Java 堆内存返回给操作系统
+
+## Java13
+
+### 引入 yield 关键字到 Switch 中
+
+- `Switch` 表达式中就多了一个关键字用于跳出 `Switch` 块的关键字 `yield`,主要用于返回一个值
+
+- `yield`和 `return` 的区别在于:`return` 会直接跳出当前循环或者方法,而 `yield` 只会跳出当前 `Switch` 块,同时在使用 `yield` 时,需要有 `default` 条件
+
+ ```java
+ private static String descLanguage(String name) {
+ return switch (name) {
+ case "Java": yield "object-oriented, platform independent and secured";
+ case "Ruby": yield "a programmer's best friend";
+ default: yield name +" is a good language";
+ };
+ }
+ ```
+
+### 文本块
+
+- 解决 Java 定义多行字符串时只能通过换行转义或者换行连接符来变通支持的问题,引入**三重双引号**来定义多行文本
+
+- 两个`"""`中间的任何内容都会被解释为字符串的一部分,包括换行符
+
+ ```java
+ String json ="{\n" +
+ " \"name\":\"mkyong\",\n" +
+ " \"age\":38\n" +
+ "}\n"; // 未支持文本块之前
+ ```
+
+ ```java
+
+ String json = """
+ {
+ "name":"mkyong",
+ "age":38
+ }
+ """;
+ ```
+
+### 增强 ZGC 释放未使用内存
+
+- 在 Java 11 中是实验性的引入的 ZGC 在实际的使用中存在未能主动将未使用的内存释放给操作系统的问题
+- ZGC 堆由一组称为 ZPages 的堆区域组成。在 GC 周期中清空 ZPages 区域时,它们将被释放并返回到页面缓存 **ZPageCache** 中,此缓存中的 ZPages 按最近最少使用(LRU)的顺序,并按照大小进行组织
+- 在 Java 13 中,ZGC 将向操作系统返回被标识为长时间未使用的页面,这样它们将可以被其他进程重用
+
+### SocketAPI 重构
+
+- Java 13 为 Socket API 带来了新的底层实现方法,并且在 Java 13 中是默认使用新的 Socket 实现,使其易于发现并在排除问题同时增加可维护性
+
+### 动态应用程序类-数据共享
+
+- Java 13 中对 Java 10 中引入的 应用程序类数据共享进行了进一步的简化、改进和扩展,即:**允许在 Java 应用程序执行结束时动态进行类归档**,具体能够被归档的类包括:所有已被加载,但不属于默认基层 CDS 的应用程序类和引用类库中的类
+
+## Java14
+
+### record 关键字
+
+- 简化数据类的定义方式,使用 record 代替 class 定义的类,只需要声明属性,就可以在获得属性的访问方法,以及 toString,hashCode,equals 方法
+
+- 类似于使用 Class 定义类,同时使用了 lomobok 插件,并打上了`@Getter,@ToString,@EqualsAndHashCode`注解
+
+- 作为预览特性引入
+
+ ```java
+ /**
+ * 这个类具有两个特征
+ * 1. 所有成员属性都是final
+ * 2. 全部方法由构造方法,和两个成员属性访问器组成(共三个)
+ * 那么这种类就很适合使用record来声明
+ */
+ final class Rectangle implements Shape {
+ final double length;
+ final double width;
+
+ public Rectangle(double length, double width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ double length() { return length; }
+ double width() { return width; }
+ }
+ /**
+ * 1. 使用record声明的类会自动拥有上面类中的三个方法
+ * 2. 在这基础上还附赠了equals(),hashCode()方法以及toString()方法
+ * 3. toString方法中包括所有成员属性的字符串表示形式及其名称
+ */
+ record Rectangle(float length, float width) { }
+ ```
+
+### 空指针异常精准提示
+
+- 通过 JVM 参数中添加`-XX:+ShowCodeDetailsInExceptionMessages`,可以在空指针异常中获取更为详细的调用信息,更快的定位和解决问题
+
+ ```java
+ a.b.c.i = 99; // 假设这段代码会发生空指针
+ ```
+
+ ```java
+ Exception in thread "main" java.lang.NullPointerException:
+ Cannot read field 'c' because 'a.b' is null.
+ at Prog.main(Prog.java:5) // 增加参数后提示的异常中很明确的告知了哪里为空导致
+ ```
+
+### switch 的增强终于转正
+
+- JDK12 引入的 switch(预览特性)在 JDK14 变为正式版本,不需要增加参数来启用,直接在 JDK14 中就能使用
+- 主要是用`->`来替代以前的`:`+`break`;另外就是提供了 yield 来在 block 中返回值
+
+_Before Java 14_
+
+```java
+switch (day) {
+ case MONDAY:
+ case FRIDAY:
+ case SUNDAY:
+ System.out.println(6);
+ break;
+ case TUESDAY:
+ System.out.println(7);
+ break;
+ case THURSDAY:
+ case SATURDAY:
+ System.out.println(8);
+ break;
+ case WEDNESDAY:
+ System.out.println(9);
+ break;
+}
+```
+
+_Java 14 enhancements_
+
+```java
+switch (day) {
+ case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
+ case TUESDAY -> System.out.println(7);
+ case THURSDAY, SATURDAY -> System.out.println(8);
+ case WEDNESDAY -> System.out.println(9);
+}
+```
+
+### instanceof 增强
+
+- instanceof 主要在**类型强转前探测对象的具体类型**,然后执行具体的强转
+
+- 新版的 instanceof 可以在判断的是否属于具体的类型同时完成转换
+
+```java
+Object obj = "我是字符串";
+if(obj instanceof String str){
+ System.out.println(str);
+}
+```
+
+### 其他特性
+
+- 从 Java11 引入的 ZGC 作为继 G1 过后的下一代 GC 算法,从支持 Linux 平台到 Java14 开始支持 MacOS 和 Window(个人感觉是终于可以在日常开发工具中先体验下 ZGC 的效果了,虽然其实 G1 也够用)
+- 移除了 CMS 垃圾收集器(功成而退)
+- 新增了 jpackage 工具,标配将应用打成 jar 包外,还支持不同平台的特性包,比如 linux 下的`deb`和`rpm`,window 平台下的`msi`和`exe`
+
+## 总结
+
+### 关于预览特性
+
+- 先贴一段 oracle 官网原文:`This is a preview feature, which is a feature whose design, specification, and implementation are complete, but is not permanent, which means that the feature may exist in a different form or not at all in future JDK releases. To compile and run code that contains preview features, you must specify additional command-line options.`
+- 这是一个预览功能,该功能的设计,规格和实现是完整的,但不是永久性的,这意味着该功能可能以其他形式存在或在将来的 JDK 版本中根本不存在。 要编译和运行包含预览功能的代码,必须指定其他命令行选项。
+- 就以`switch`的增强为例子,从 Java12 中推出,到 Java13 中将继续增强,直到 Java14 才正式转正进入 JDK 可以放心使用,不用考虑后续 JDK 版本对其的改动或修改
+- 一方面可以看出 JDK 作为标准平台在增加新特性的严谨态度,另一方面个人认为是对于预览特性应该采取审慎使用的态度。特性的设计和实现容易,但是其实际价值依然需要在使用中去验证
+
+### JVM 虚拟机优化
+
+- 每次 Java 版本的发布都伴随着对 JVM 虚拟机的优化,包括对现有垃圾回收算法的改进,引入新的垃圾回收算法,移除老旧的不再适用于今天的垃圾回收算法等
+- 整体优化的方向是**高效,低时延的垃圾回收表现**
+- 对于日常的应用开发者可能比较关注新的语法特性,但是从一个公司角度来说,在考虑是否升级 Java 平台时更加考虑的是**JVM 运行时的提升**
+
+## 参考信息
+
+- IBM Developer Java9
+- Guide to Java10
+- Java 10 新特性介绍
+- IBM Devloper Java11
+- Java 11 – Features and Comparison:
+- Oracle Java12 ReleaseNote
+- Oracle Java13 ReleaseNote
+- New Java13 Features
+- Java13 新特性概述
+- Oracle Java14 record
+- java14-features
\ No newline at end of file
diff --git a/docs/java/jvm/GC调优参数.md b/docs/java/jvm/GC调优参数.md
new file mode 100644
index 00000000..b9475b08
--- /dev/null
+++ b/docs/java/jvm/GC调优参数.md
@@ -0,0 +1,58 @@
+> 原文地址: https://juejin.im/post/5c94a123f265da610916081f。
+
+## JVM 配置常用参数
+
+1. 堆参数;
+2. 回收器参数;
+3. 项目中常用配置;
+4. 常用组合;
+
+### 堆参数
+
+
+
+### 回收器参数
+
+
+
+如上表所示,目前**主要有串行、并行和并发三种**,对于大内存的应用而言,串行的性能太低,因此使用到的主要是并行和并发两种。并行和并发 GC 的策略通过 `UseParallelGC `和` UseConcMarkSweepGC` 来指定,还有一些细节的配置参数用来配置策略的执行方式。例如:`XX:ParallelGCThreads`, `XX:CMSInitiatingOccupancyFraction` 等。 通常:Young 区对象回收只可选择并行(耗时间),Old 区选择并发(耗 CPU)。
+
+### 项目中常用配置
+
+> 备注:在Java8中永久代的参数`-XX:PermSize` 和`-XX:MaxPermSize`已经失效。
+
+
+
+### 常用组合
+
+
+
+## 常用 GC 调优策略
+
+1. GC 调优原则;
+2. GC 调优目的;
+3. GC 调优策略;
+
+### GC 调优原则
+
+在调优之前,我们需要记住下面的原则:
+
+> 多数的 Java 应用不需要在服务器上进行 GC 优化; 多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题; 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合); 减少创建对象的数量; 减少使用全局变量和大对象; GC 优化是到最后不得已才采用的手段; 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多。
+
+### GC 调优目的
+
+将转移到老年代的对象数量降低到最小; 减少 GC 的执行时间。
+
+### GC 调优策略
+
+**策略 1:**将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
+
+**策略 2:**大对象进入老年代,虽然大部分情况下,将对象分配在新生代是合理的。但是对于大对象这种做法却值得商榷,大对象如果首次在新生代分配可能会出现空间不足导致很多年龄不够的小对象被分配的老年代,破坏新生代的对象结构,可能会出现频繁的 full gc。因此,对于大对象,可以设置直接进入老年代(当然短命的大对象对于垃圾回收来说简直就是噩梦)。`-XX:PretenureSizeThreshold` 可以设置直接进入老年代的对象大小。
+
+**策略 3:**合理设置进入老年代对象的年龄,`-XX:MaxTenuringThreshold` 设置对象进入老年代的年龄大小,减少老年代的内存占用,降低 full gc 发生的频率。
+
+**策略 4:**设置稳定的堆大小,堆大小设置有两个参数:`-Xms` 初始化堆大小,`-Xmx` 最大堆大小。
+
+**策略5:**注意: 如果满足下面的指标,**则一般不需要进行 GC 优化:**
+
+> MinorGC 执行时间不到50ms; Minor GC 执行不频繁,约10秒一次; Full GC 执行时间不到1s; Full GC 执行频率不算频繁,不低于10分钟1次。
\ No newline at end of file
diff --git a/docs/java/jvm/JDK监控和故障处理工具总结.md b/docs/java/jvm/JDK监控和故障处理工具总结.md
new file mode 100644
index 00000000..8a8ec160
--- /dev/null
+++ b/docs/java/jvm/JDK监控和故障处理工具总结.md
@@ -0,0 +1,337 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [JDK 监控和故障处理工具总结](#jdk-监控和故障处理工具总结)
+ - [JDK 命令行工具](#jdk-命令行工具)
+ - [`jps`:查看所有 Java 进程](#jps查看所有-java-进程)
+ - [`jstat`: 监视虚拟机各种运行状态信息](#jstat-监视虚拟机各种运行状态信息)
+ - [` jinfo`: 实时地查看和调整虚拟机各项参数](#-jinfo-实时地查看和调整虚拟机各项参数)
+ - [`jmap`:生成堆转储快照](#jmap生成堆转储快照)
+ - [**`jhat`**: 分析 heapdump 文件](#jhat-分析-heapdump-文件)
+ - [**`jstack`** :生成虚拟机当前时刻的线程快照](#jstack-生成虚拟机当前时刻的线程快照)
+ - [JDK 可视化分析工具](#jdk-可视化分析工具)
+ - [JConsole:Java 监视与管理控制台](#jconsolejava-监视与管理控制台)
+ - [连接 Jconsole](#连接-jconsole)
+ - [查看 Java 程序概况](#查看-java-程序概况)
+ - [内存监控](#内存监控)
+ - [线程监控](#线程监控)
+ - [Visual VM:多合一故障处理工具](#visual-vm多合一故障处理工具)
+
+
+
+# JDK 监控和故障处理工具总结
+
+## JDK 命令行工具
+
+这些命令在 JDK 安装目录下的 bin 目录下:
+
+- **`jps`** (JVM Process Status): 类似 UNIX 的 `ps` 命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
+- **`jstat`**( JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;
+- **`jinfo`** (Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息;
+- **`jmap`** (Memory Map for Java) :生成堆转储快照;
+- **`jhat`** (JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;
+- **`jstack`** (Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
+
+### `jps`:查看所有 Java 进程
+
+`jps`(JVM Process Status) 命令类似 UNIX 的 `ps` 命令。
+
+`jps`:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID(Local Virtual Machine Identifier,LVMID)。`jps -q` :只输出进程的本地虚拟机唯一 ID。
+
+```powershell
+C:\Users\SnailClimb>jps
+7360 NettyClient2
+17396
+7972 Launcher
+16504 Jps
+17340 NettyServer
+```
+
+`jps -l`:输出主类的全名,如果进程执行的是 Jar 包,输出 Jar 路径。
+
+```powershell
+C:\Users\SnailClimb>jps -l
+7360 firstNettyDemo.NettyClient2
+17396
+7972 org.jetbrains.jps.cmdline.Launcher
+16492 sun.tools.jps.Jps
+17340 firstNettyDemo.NettyServer
+```
+
+`jps -v`:输出虚拟机进程启动时 JVM 参数。
+
+`jps -m`:输出传递给 Java 进程 main() 函数的参数。
+
+### `jstat`: 监视虚拟机各种运行状态信息
+
+jstat(JVM Statistics Monitoring Tool) 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程(需要远程主机提供 RMI 支持)虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。
+
+**`jstat` 命令使用格式:**
+
+```powershell
+jstat - [-t] [-h] [ []]
+```
+
+比如 `jstat -gc -h3 31736 1000 10`表示分析进程 id 为 31736 的 gc 情况,每隔 1000ms 打印一次记录,打印 10 次停止,每 3 行后打印指标头部。
+
+**常见的 option 如下:**
+
+- `jstat -class vmid` :显示 ClassLoader 的相关信息;
+- `jstat -compiler vmid` :显示 JIT 编译的相关信息;
+- `jstat -gc vmid` :显示与 GC 相关的堆信息;
+- `jstat -gccapacity vmid` :显示各个代的容量及使用情况;
+- `jstat -gcnew vmid` :显示新生代信息;
+- `jstat -gcnewcapcacity vmid` :显示新生代大小与使用情况;
+- `jstat -gcold vmid` :显示老年代和永久代的信息;
+- `jstat -gcoldcapacity vmid` :显示老年代的大小;
+- `jstat -gcpermcapacity vmid` :显示永久代大小;
+- `jstat -gcutil vmid` :显示垃圾收集信息;
+
+另外,加上 `-t`参数可以在输出信息上加一个 Timestamp 列,显示程序的运行时间。
+
+### ` jinfo`: 实时地查看和调整虚拟机各项参数
+
+`jinfo vmid` :输出当前 jvm 进程的全部参数和系统属性 (第一部分是系统的属性,第二部分是 JVM 的参数)。
+
+`jinfo -flag name vmid` :输出对应名称的参数的具体值。比如输出 MaxHeapSize、查看当前 jvm 进程是否开启打印 GC 日志 ( `-XX:PrintGCDetails` :详细 GC 日志模式,这两个都是默认关闭的)。
+
+```powershell
+C:\Users\SnailClimb>jinfo -flag MaxHeapSize 17340
+-XX:MaxHeapSize=2124414976
+C:\Users\SnailClimb>jinfo -flag PrintGC 17340
+-XX:-PrintGC
+```
+
+使用 jinfo 可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。尤其在线上的环境特别有用,请看下面的例子:
+
+`jinfo -flag [+|-]name vmid` 开启或者关闭对应名称的参数。
+
+```powershell
+C:\Users\SnailClimb>jinfo -flag PrintGC 17340
+-XX:-PrintGC
+
+C:\Users\SnailClimb>jinfo -flag +PrintGC 17340
+
+C:\Users\SnailClimb>jinfo -flag PrintGC 17340
+-XX:+PrintGC
+```
+
+### `jmap`:生成堆转储快照
+
+`jmap`(Memory Map for Java)命令用于生成堆转储快照。 如果不使用 `jmap` 命令,要想获取 Java 堆转储,可以使用 `“-XX:+HeapDumpOnOutOfMemoryError”` 参数,可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件,Linux 命令下可以通过 `kill -3` 发送进程退出信号也能拿到 dump 文件。
+
+`jmap` 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalizer 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前使用的是哪种收集器等。和`jinfo`一样,`jmap`有不少功能在 Windows 平台下也是受限制的。
+
+示例:将指定应用程序的堆快照输出到桌面。后面,可以通过 jhat、Visual VM 等工具分析该堆文件。
+
+```powershell
+C:\Users\SnailClimb>jmap -dump:format=b,file=C:\Users\SnailClimb\Desktop\heap.hprof 17340
+Dumping heap to C:\Users\SnailClimb\Desktop\heap.hprof ...
+Heap dump file created
+```
+
+### **`jhat`**: 分析 heapdump 文件
+
+ **`jhat`** 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。
+
+```powershell
+C:\Users\SnailClimb>jhat C:\Users\SnailClimb\Desktop\heap.hprof
+Reading from C:\Users\SnailClimb\Desktop\heap.hprof...
+Dump file created Sat May 04 12:30:31 CST 2019
+Snapshot read, resolving...
+Resolving 131419 objects...
+Chasing references, expect 26 dots..........................
+Eliminating duplicate references..........................
+Snapshot resolved.
+Started HTTP server on port 7000
+Server is ready.
+```
+
+访问
+
+### **`jstack`** :生成虚拟机当前时刻的线程快照
+
+`jstack`(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合.
+
+生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过`jstack`来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
+
+**下面是一个线程死锁的代码。我们下面会通过 `jstack` 命令进行死锁检查,输出死锁信息,找到发生死锁的线程。**
+
+```java
+public class DeadLockDemo {
+ private static Object resource1 = new Object();//资源 1
+ private static Object resource2 = new Object();//资源 2
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 1").start();
+
+ new Thread(() -> {
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource1");
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ }
+ }
+ }, "线程 2").start();
+ }
+}
+```
+
+Output
+
+```
+Thread[线程 1,5,main]get resource1
+Thread[线程 2,5,main]get resource2
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 2,5,main]waiting get resource1
+```
+
+线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
+
+**通过 `jstack` 命令分析:**
+
+```powershell
+C:\Users\SnailClimb>jps
+13792 KotlinCompileDaemon
+7360 NettyClient2
+17396
+7972 Launcher
+8932 Launcher
+9256 DeadLockDemo
+10764 Jps
+17340 NettyServer
+
+C:\Users\SnailClimb>jstack 9256
+```
+
+输出的部分内容如下:
+
+```powershell
+Found one Java-level deadlock:
+=============================
+"线程 2":
+ waiting to lock monitor 0x000000000333e668 (object 0x00000000d5efe1c0, a java.lang.Object),
+ which is held by "线程 1"
+"线程 1":
+ waiting to lock monitor 0x000000000333be88 (object 0x00000000d5efe1d0, a java.lang.Object),
+ which is held by "线程 2"
+
+Java stack information for the threads listed above:
+===================================================
+"线程 2":
+ at DeadLockDemo.lambda$main$1(DeadLockDemo.java:31)
+ - waiting to lock <0x00000000d5efe1c0> (a java.lang.Object)
+ - locked <0x00000000d5efe1d0> (a java.lang.Object)
+ at DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
+ at java.lang.Thread.run(Thread.java:748)
+"线程 1":
+ at DeadLockDemo.lambda$main$0(DeadLockDemo.java:16)
+ - waiting to lock <0x00000000d5efe1d0> (a java.lang.Object)
+ - locked <0x00000000d5efe1c0> (a java.lang.Object)
+ at DeadLockDemo$$Lambda$1/1324119927.run(Unknown Source)
+ at java.lang.Thread.run(Thread.java:748)
+
+Found 1 deadlock.
+```
+
+可以看到 `jstack` 命令已经帮我们找到发生死锁的线程的具体信息。
+
+## JDK 可视化分析工具
+
+### JConsole:Java 监视与管理控制台
+
+JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监视本地及远程服务器的 java 进程的内存使用情况。你可以在控制台输出`console`命令启动或者在 JDK 目录下的 bin 目录找到`jconsole.exe`然后双击启动。
+
+#### 连接 Jconsole
+
+
+
+如果需要使用 JConsole 连接远程进程,可以在远程 Java 程序启动时加上下面这些参数:
+
+```properties
+-Djava.rmi.server.hostname=外网访问 ip 地址
+-Dcom.sun.management.jmxremote.port=60001 //监控的端口号
+-Dcom.sun.management.jmxremote.authenticate=false //关闭认证
+-Dcom.sun.management.jmxremote.ssl=false
+```
+
+在使用 JConsole 连接时,远程进程地址如下:
+
+```
+外网访问 ip 地址:60001
+```
+
+#### 查看 Java 程序概况
+
+
+
+#### 内存监控
+
+JConsole 可以显示当前内存的详细信息。不仅包括堆内存/非堆内存的整体信息,还可以细化到 eden 区、survivor 区等的使用情况,如下图所示。
+
+点击右边的“执行 GC(G)”按钮可以强制应用程序执行一个 Full GC。
+
+> - **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
+> - **老年代 GC(Major GC/Full GC)**:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
+
+
+
+#### 线程监控
+
+类似我们前面讲的 `jstack` 命令,不过这个是可视化的。
+
+最下面有一个"检测死锁 (D)"按钮,点击这个按钮可以自动为你找到发生死锁的线程以及它们的详细信息 。
+
+
+
+### Visual VM:多合一故障处理工具
+
+VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。在 VisualVM 的图形用户界面中,您可以方便、快捷地查看多个 Java 应用程序的相关信息。Visual VM 官网: 。Visual VM 中文文档:。
+
+下面这段话摘自《深入理解 Java 虚拟机》。
+
+> VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随 JDK 发布的功能最强大的运行监视和故障处理程序,官方在 VisualVM 的软件说明中写上了“All-in-One”的描述字样,预示着他除了运行监视、故障处理外,还提供了很多其他方面的功能,如性能分析(Profiling)。VisualVM 的性能分析功能甚至比起 JProfiler、YourKit 等专业且收费的 Profiling 工具都不会逊色多少,而且 VisualVM 还有一个很大的优点:不需要被监视的程序基于特殊 Agent 运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是 JProfiler、YourKit 等工具无法与之媲美的。
+
+ VisualVM 基于 NetBeans 平台开发,因此他一开始就具备了插件扩展功能的特性,通过插件扩展支持,VisualVM 可以做到:
+
+- **显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。**
+- **监视应用程序的 CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。**
+- **dump 以及分析堆转储快照(jmap、jhat)。**
+- **方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。**
+- **离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。**
+- **其他 plugins 的无限的可能性......**
+
+这里就不具体介绍 VisualVM 的使用,如果想了解的话可以看:
+
+-
+-
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/jvm/JVM垃圾回收.md b/docs/java/jvm/JVM垃圾回收.md
new file mode 100644
index 00000000..15e09753
--- /dev/null
+++ b/docs/java/jvm/JVM垃圾回收.md
@@ -0,0 +1,443 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [JVM 垃圾回收](#jvm-垃圾回收)
+ - [写在前面](#写在前面)
+ - [本节常见面试题](#本节常见面试题)
+ - [本文导火索](#本文导火索)
+ - [1 揭开 JVM 内存分配与回收的神秘面纱](#1--揭开-jvm-内存分配与回收的神秘面纱)
+ - [1.1 对象优先在 eden 区分配](#11-对象优先在-eden-区分配)
+ - [1.2 大对象直接进入老年代](#12-大对象直接进入老年代)
+ - [1.3 长期存活的对象将进入老年代](#13-长期存活的对象将进入老年代)
+ - [1.4 动态对象年龄判定](#14-动态对象年龄判定)
+ - [2 对象已经死亡?](#2-对象已经死亡)
+ - [2.1 引用计数法](#21-引用计数法)
+ - [2.2 可达性分析算法](#22-可达性分析算法)
+ - [2.3 再谈引用](#23-再谈引用)
+ - [2.4 不可达的对象并非“非死不可”](#24-不可达的对象并非非死不可)
+ - [2.5 如何判断一个常量是废弃常量](#25-如何判断一个常量是废弃常量)
+ - [2.6 如何判断一个类是无用的类](#26-如何判断一个类是无用的类)
+ - [3 垃圾收集算法](#3-垃圾收集算法)
+ - [3.1 标记-清除算法](#31-标记-清除算法)
+ - [3.2 复制算法](#32-复制算法)
+ - [3.3 标记-整理算法](#33-标记-整理算法)
+ - [3.4 分代收集算法](#34-分代收集算法)
+ - [4 垃圾收集器](#4-垃圾收集器)
+ - [4.1 Serial 收集器](#41-serial-收集器)
+ - [4.2 ParNew 收集器](#42-parnew-收集器)
+ - [4.3 Parallel Scavenge 收集器](#43-parallel-scavenge-收集器)
+ - [4.4.Serial Old 收集器](#44serial-old-收集器)
+ - [4.5 Parallel Old 收集器](#45-parallel-old-收集器)
+ - [4.6 CMS 收集器](#46-cms-收集器)
+ - [4.7 G1 收集器](#47-g1-收集器)
+ - [参考](#参考)
+
+
+# JVM 垃圾回收
+
+## 写在前面
+
+### 本节常见面试题
+
+问题答案在文中都有提到
+
+- 如何判断对象是否死亡(两种方法)。
+- 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
+- 如何判断一个常量是废弃常量
+- 如何判断一个类是无用的类
+- 垃圾收集有哪些算法,各自的特点?
+- HotSpot 为什么要分为新生代和老年代?
+- 常见的垃圾回收器有哪些?
+- 介绍一下 CMS,G1 收集器。
+- Minor Gc 和 Full GC 有什么不同呢?
+
+### 本文导火索
+
+
+
+当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
+
+## 1 揭开 JVM 内存分配与回收的神秘面纱
+
+Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。
+
+Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
+
+**堆空间的基本结构:**
+
+
+
+
+
+
+上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。
+
+
+
+### 1.1 对象优先在 eden 区分配
+
+目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
+
+大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。
+
+在测试之前我们先来看看 **Minor GC 和 Full GC 有什么不同呢?**
+
+- **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
+- **老年代 GC(Major GC/Full GC)**:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
+
+> [issue#664 ](https://github.com/Snailclimb/JavaGuide/issues/664) :**[guang19](https://github.com/guang19)** 补充:个人在网上查阅相关资料的时候发现如题所说的观点。有的文章说 Full GC与Major GC一样是属于对老年代的GC,也有的文章说 Full GC 是对整个堆区的GC,所以这点需要各位同学自行分辨Full GC语义。见: [知乎讨论](https://www.zhihu.com/question/41922036)
+
+**测试:**
+
+```java
+public class GCTest {
+
+ public static void main(String[] args) {
+ byte[] allocation1, allocation2;
+ allocation1 = new byte[30900*1024];
+ //allocation2 = new byte[900*1024];
+ }
+}
+```
+通过以下方式运行:
+
+
+添加的参数:`-XX:+PrintGCDetails`
+
+
+运行结果 (红色字体描述有误,应该是对应于 JDK1.7 的永久代):
+
+
+
+从上图我们可以看出 eden 区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用 2000 多 k 内存)。假如我们再为 allocation2 分配内存会出现什么情况呢?
+
+```java
+allocation2 = new byte[900*1024];
+```
+
+
+**简单解释一下为什么会出现这种情况:** 因为给 allocation2 分配内存的时候 eden 区内存几乎已经被分配完了,我们刚刚讲了当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.GC 期间虚拟机又发现 allocation1 无法存入 Survivor 空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 eden 区的话,还是会在 eden 区分配内存。可以执行如下代码验证:
+
+```java
+public class GCTest {
+
+ public static void main(String[] args) {
+ byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
+ allocation1 = new byte[32000*1024];
+ allocation2 = new byte[1000*1024];
+ allocation3 = new byte[1000*1024];
+ allocation4 = new byte[1000*1024];
+ allocation5 = new byte[1000*1024];
+ }
+}
+
+```
+
+
+### 1.2 大对象直接进入老年代
+大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
+
+**为什么要这样呢?**
+
+为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
+
+### 1.3 长期存活的对象将进入老年代
+既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。
+
+如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
+
+### 1.4 动态对象年龄判定
+
+
+大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
+
+> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。
+>
+> **动态年龄计算的代码如下**
+>
+> ```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;
+> ...
+> }
+>
+> ```
+>
+> 额外补充说明([issue672](https://github.com/Snailclimb/JavaGuide/issues/672)):**关于默认的晋升年龄是15,这个说法的来源大部分都是《深入理解Java虚拟机》这本书。**
+> 如果你去Oracle的官网阅读[相关的虚拟机参数](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html),你会发现`-XX:MaxTenuringThreshold=threshold`这里有个说明
+>
+> **Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.默认晋升年龄并不都是15,这个是要区分垃圾收集器的,CMS就是6.**
+
+
+## 2 对象已经死亡?
+
+堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
+
+
+
+### 2.1 引用计数法
+
+给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
+
+**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
+
+```java
+public class ReferenceCountingGc {
+ Object instance = null;
+ public static void main(String[] args) {
+ ReferenceCountingGc objA = new ReferenceCountingGc();
+ ReferenceCountingGc objB = new ReferenceCountingGc();
+ objA.instance = objB;
+ objB.instance = objA;
+ objA = null;
+ objB = null;
+
+ }
+}
+```
+
+
+
+### 2.2 可达性分析算法
+
+这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
+
+
+
+
+### 2.3 再谈引用
+
+无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
+
+JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
+
+JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
+
+**1.强引用(StrongReference)**
+
+以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
+
+**2.软引用(SoftReference)**
+
+如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
+
+软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
+
+**3.弱引用(WeakReference)**
+
+如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
+
+弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
+
+**4.虚引用(PhantomReference)**
+
+"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
+
+**虚引用主要用来跟踪对象被垃圾回收的活动**。
+
+**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
+
+特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。
+
+### 2.4 不可达的对象并非“非死不可”
+
+即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
+
+被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
+
+### 2.5 如何判断一个常量是废弃常量
+
+运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
+
+假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
+
+注意:我们在 [可能是把 Java 内存区域讲的最清楚的一篇文章 ](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了 JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
+
+### 2.6 如何判断一个类是无用的类
+
+方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
+
+判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 **“无用的类”** :
+
+- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
+- 加载该类的 ClassLoader 已经被回收。
+- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
+
+虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
+
+
+## 3 垃圾收集算法
+
+
+
+### 3.1 标记-清除算法
+
+该算法分为“标记”和“清除”阶段:首先比较出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
+
+1. **效率问题**
+2. **空间问题(标记清除后会产生大量不连续的碎片)**
+
+
+
+### 3.2 复制算法
+
+为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
+
+
+
+### 3.3 标记-整理算法
+根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
+
+
+
+### 3.4 分代收集算法
+
+当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
+
+**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。**
+
+**延伸面试问题:** HotSpot 为什么要分为新生代和老年代?
+
+根据上面的对分代收集算法的介绍回答。
+
+## 4 垃圾收集器
+
+
+
+**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
+
+虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。
+
+
+### 4.1 Serial 收集器
+Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。
+
+ **新生代采用复制算法,老年代采用标记-整理算法。**
+
+
+虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
+
+但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
+
+### 4.2 ParNew 收集器
+**ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。**
+
+ **新生代采用复制算法,老年代采用标记-整理算法。**
+
+
+它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
+
+**并行和并发概念补充:**
+
+- **并行(Parallel)** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
+
+- **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。
+
+
+### 4.3 Parallel Scavenge 收集器
+
+Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 **那么它有什么特别之处呢?**
+
+```
+-XX:+UseParallelGC
+
+ 使用 Parallel 收集器+ 老年代串行
+
+-XX:+UseParallelOldGC
+
+ 使用 Parallel 收集器+ 老年代并行
+
+```
+
+**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
+
+ **新生代采用复制算法,老年代采用标记-整理算法。**
+
+
+
+### 4.4.Serial Old 收集器
+**Serial 收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
+
+### 4.5 Parallel Old 收集器
+ **Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
+
+### 4.6 CMS 收集器
+
+**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。**
+
+**CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。**
+
+从名字中的**Mark Sweep**这两个词可以看出,CMS 收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
+
+- **初始标记:** 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
+- **并发标记:** 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
+- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
+- **并发清除:** 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
+
+
+
+从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点:
+
+- **对 CPU 资源敏感;**
+- **无法处理浮动垃圾;**
+- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**
+
+### 4.7 G1 收集器
+
+
+**G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.**
+
+被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
+
+- **并行与并发**:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
+- **分代收集**:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
+- **空间整合**:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
+- **可预测的停顿**:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
+
+
+G1 收集器的运作大致分为以下几个步骤:
+
+- **初始标记**
+- **并发标记**
+- **最终标记**
+- **筛选回收**
+
+
+**G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)**。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
+
+## 参考
+
+- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》
+- https://my.oschina.net/hosee/blog/644618
+-
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/java/jvm/Java内存区域.md b/docs/java/jvm/Java内存区域.md
new file mode 100644
index 00000000..050401f9
--- /dev/null
+++ b/docs/java/jvm/Java内存区域.md
@@ -0,0 +1,507 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [Java 内存区域详解](#java-内存区域详解)
+ - [写在前面 (常见面试题)](#写在前面-常见面试题)
+ - [基本问题](#基本问题)
+ - [拓展问题](#拓展问题)
+ - [一 概述](#一-概述)
+ - [二 运行时数据区域](#二-运行时数据区域)
+ - [2.1 程序计数器](#21-程序计数器)
+ - [2.2 Java 虚拟机栈](#22-java-虚拟机栈)
+ - [2.3 本地方法栈](#23-本地方法栈)
+ - [2.4 堆](#24-堆)
+ - [2.5 方法区](#25-方法区)
+ - [2.5.1 方法区和永久代的关系](#251-方法区和永久代的关系)
+ - [2.5.2 常用参数](#252-常用参数)
+ - [2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?](#253-为什么要将永久代-permgen-替换为元空间-metaspace-呢)
+ - [2.6 运行时常量池](#26-运行时常量池)
+ - [2.7 直接内存](#27-直接内存)
+ - [三 HotSpot 虚拟机对象探秘](#三-hotspot-虚拟机对象探秘)
+ - [3.1 对象的创建](#31-对象的创建)
+ - [Step1:类加载检查](#step1类加载检查)
+ - [Step2:分配内存](#step2分配内存)
+ - [Step3:初始化零值](#step3初始化零值)
+ - [Step4:设置对象头](#step4设置对象头)
+ - [Step5:执行 init 方法](#step5执行-init-方法)
+ - [3.2 对象的内存布局](#32-对象的内存布局)
+ - [3.3 对象的访问定位](#33-对象的访问定位)
+ - [四 重点补充内容](#四--重点补充内容)
+ - [4.1 String 类和常量池](#41-string-类和常量池)
+ - [4.2 String s1 = new String("abc");这句话创建了几个字符串对象?](#42-string-s1--new-stringabc这句话创建了几个字符串对象)
+ - [4.3 8 种基本类型的包装类和常量池](#43-8-种基本类型的包装类和常量池)
+ - [参考](#参考)
+ - [公众号](#公众号)
+
+
+
+# Java 内存区域详解
+
+如果没有特殊说明,都是针对的是 HotSpot 虚拟机。
+
+## 写在前面 (常见面试题)
+
+### 基本问题
+
+- **介绍下 Java 内存区域(运行时数据区)**
+- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)**
+- **对象的访问定位的两种方式(句柄和直接指针两种方式)**
+
+### 拓展问题
+
+- **String 类和常量池**
+- **8 种基本类型的包装类和常量池**
+
+## 一 概述
+
+对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
+
+## 二 运行时数据区域
+Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。
+
+**JDK 1.8 之前:**
+
+
+
+
+**JDK 1.8 :**
+
+
+
+
+
+**线程私有的:**
+
+- 程序计数器
+- 虚拟机栈
+- 本地方法栈
+
+**线程共享的:**
+
+- 堆
+- 方法区
+- 直接内存 (非运行时数据区的一部分)
+
+### 2.1 程序计数器
+程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。**
+
+另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。**
+
+**从上面的介绍中我们知道程序计数器主要有两个作用:**
+
+1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
+
+**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。**
+
+### 2.2 Java 虚拟机栈
+
+**与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。**
+
+**Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
+
+**局部变量表主要存放了编译器可知的各种数据类型**(boolean、byte、char、short、int、float、long、double)、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
+
+**Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。**
+
+- **StackOverFlowError:** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
+- **OutOfMemoryError:** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。
+
+Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
+
+**扩展:那么方法/函数如何调用?**
+
+Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
+
+Java 方法有两种返回方式:
+
+1. return 语句。
+2. 抛出异常。
+
+不管哪种返回方式都会导致栈帧被弹出。
+
+### 2.3 本地方法栈
+
+和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
+
+本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
+
+方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种错误。
+
+### 2.4 堆
+
+Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
+
+**Java世界中“几乎”所有的对象都在堆中分配,但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从jdk 1.7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。**
+
+Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
+
+在 JDK 7 版本及JDK 7 版本之前,堆内存被通常被分为下面三部分:
+
+1. 新生代内存(Young Generation)
+2. 老生代(Old Generation)
+3. 永生代(Permanent Generation)
+
+
+
+JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
+
+
+
+**上图所示的 Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to),中间一层属于老年代。**
+
+大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
+
+> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。
+>
+> **动态年龄计算的代码如下**
+>
+> ```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;
+> ...
+> }
+>
+> ```
+>
+>
+
+堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:
+
+1. **`OutOfMemoryError: GC Overhead Limit Exceeded`** : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
+2. **`java.lang.OutOfMemoryError: Java heap space`** :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发`java.lang.OutOfMemoryError: Java heap space` 错误。(和本机物理内存无关,和你配置的内存大小有关!)
+3. ......
+
+### 2.5 方法区
+
+方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 **Java 虚拟机规范把方法区描述为堆的一个逻辑部分**,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。
+
+方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。
+
+#### 2.5.1 方法区和永久代的关系
+
+> 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 **方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
+
+#### 2.5.2 常用参数
+
+JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
+
+```java
+-XX:PermSize=N //方法区 (永久代) 初始大小
+-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
+```
+
+相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
+
+JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
+
+下面是一些常用参数:
+
+```java
+-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
+-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
+```
+
+与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
+
+#### 2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
+
+1. 整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
+>当你元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace`
+
+你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
+
+2. 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 `MaxPermSize` 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
+
+3. 在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
+
+### 2.6 运行时常量池
+
+运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用)
+
+既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。
+
+~~**JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。**~~
+
+> 修正([issue747](https://github.com/Snailclimb/JavaGuide/issues/747),[reference](https://blog.csdn.net/q5706503/article/details/84640762)):
+>
+> 1. **JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代**
+> 2. **JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代** 。
+> 3. **JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)**
+>
+>
+>
+
+相关问题:JVM 常量池中存储的是对象还是引用呢?: https://www.zhihu.com/question/57109429/answer/151717241 by RednaxelaFX
+
+
+### 2.7 直接内存
+
+**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。**
+
+JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。
+
+本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
+
+
+## 三 HotSpot 虚拟机对象探秘
+通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
+
+### 3.1 对象的创建
+下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
+
+
+#### Step1:类加载检查
+
+ 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
+
+#### Step2:分配内存
+
+在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。
+
+
+**内存分配的两种方式:(补充内容,需要掌握)**
+
+选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
+
+
+
+**内存分配并发问题(补充内容,需要掌握)**
+
+在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
+
+- **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。**
+- **TLAB:** 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
+
+#### Step3:初始化零值
+
+内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
+
+#### Step4:设置对象头
+
+初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
+
+#### Step5:执行 init 方法
+
+ 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
+
+
+### 3.2 对象的内存布局
+
+在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:**对象头**、**实例数据**和**对齐填充**。
+
+**Hotspot 虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的运行时数据**(哈希码、GC 分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
+
+**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。
+
+**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
+
+### 3.3 对象的访问定位
+建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种:
+
+1. **句柄:** 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
+
+ 
+
+2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
+
+
+
+**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。**
+
+
+## 四 重点补充内容
+
+### 4.1 String 类和常量池
+
+**String 对象的两种创建方式:**
+
+```java
+String str1 = "abcd";//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";
+String str2 = new String("abcd");//堆中创建一个新的对象
+String str3 = new String("abcd");//堆中创建一个新的对象
+System.out.println(str1==str2);//false
+System.out.println(str2==str3);//false
+```
+
+这两种不同的创建方法是有差别的。
+
+- 第一种方式是在常量池中拿对象;
+- 第二种方式是直接在堆内存空间创建一个新的对象。
+
+记住一点:**只要使用 new 方法,便需要创建新的对象。**
+
+再给大家一个图应该更容易理解,图片来源::
+
+
+
+**String 类型的常量池比较特殊。它的主要使用方法有两种:**
+
+- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
+- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.7之前(不包含1.7)的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7以及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。
+
+```java
+ String s1 = new String("计算机");
+ String s2 = s1.intern();
+ String s3 = "计算机";
+ System.out.println(s2);//计算机
+ System.out.println(s1 == s2);//false,因为一个是堆内存中的 String 对象一个是常量池中的 String 对象,
+ System.out.println(s3 == s2);//true,因为两个都是常量池中的 String 对象
+```
+**字符串拼接:**
+
+```java
+ String str1 = "str";
+ String str2 = "ing";
+
+ String str3 = "str" + "ing";//常量池中的对象
+ String str4 = str1 + str2; //在堆上创建的新的对象
+ String str5 = "string";//常量池中的对象
+ System.out.println(str3 == str4);//false
+ System.out.println(str3 == str5);//true
+ System.out.println(str4 == str5);//false
+```
+
+
+尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
+### 4.2 String s1 = new String("abc");这句话创建了几个字符串对象?
+
+**将创建 1 或 2 个字符串。如果池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果池中没有字符串常量“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。**
+
+**验证:**
+
+```java
+ String s1 = new String("abc");// 堆内存的地址值
+ String s2 = "abc";
+ System.out.println(s1 == s2);// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
+ System.out.println(s1.equals(s2));// 输出 true
+```
+
+**结果:**
+
+```
+false
+true
+```
+
+### 4.3 8 种基本类型的包装类和常量池
+
+**Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象。** 为啥把缓存设置为[-128,127]区间?([参见issue/461](https://github.com/Snailclimb/JavaGuide/issues/461))性能和资源之间的权衡。
+
+```java
+public static Boolean valueOf(boolean b) {
+ return (b ? TRUE : FALSE);
+}
+```
+
+```java
+private static class CharacterCache {
+ private CharacterCache(){}
+
+ static final Character cache[] = new Character[127 + 1];
+ static {
+ for (int i = 0; i < cache.length; i++)
+ cache[i] = new Character((char)i);
+ }
+}
+```
+
+两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**
+
+```java
+ Integer i1 = 33;
+ Integer i2 = 33;
+ System.out.println(i1 == i2);// 输出 true
+ Integer i11 = 333;
+ Integer i22 = 333;
+ System.out.println(i11 == i22);// 输出 false
+ Double i3 = 1.2;
+ Double i4 = 1.2;
+ System.out.println(i3 == i4);// 输出 false
+```
+
+**Integer 缓存源代码:**
+
+```java
+/**
+*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
+*/
+ public static Integer valueOf(int i) {
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+ return new Integer(i);
+ }
+
+```
+
+**应用场景:**
+1. Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
+2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
+
+```java
+ Integer i1 = 40;
+ Integer i2 = new Integer(40);
+ System.out.println(i1==i2);//输出 false
+```
+**Integer 比较更丰富的一个例子:**
+
+```java
+ Integer i1 = 40;
+ Integer i2 = 40;
+ Integer i3 = 0;
+ Integer i4 = new Integer(40);
+ Integer i5 = new Integer(40);
+ Integer i6 = new Integer(0);
+
+ System.out.println("i1=i2 " + (i1 == i2));
+ System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
+ System.out.println("i1=i4 " + (i1 == i4));
+ System.out.println("i4=i5 " + (i4 == i5));
+ System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
+ System.out.println("40=i5+i6 " + (40 == i5 + i6));
+```
+
+结果:
+
+```
+i1=i2 true
+i1=i2+i3 true
+i1=i4 false
+i4=i5 false
+i4=i5+i6 true
+40=i5+i6 true
+```
+
+解释:
+
+语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较。
+
+## 参考
+
+- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》
+- 《实战 java 虚拟机》
+-
+-
+-
+-
+- 深入解析String#intern
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/jvm/[加餐]大白话带你认识JVM.md b/docs/java/jvm/[加餐]大白话带你认识JVM.md
new file mode 100644
index 00000000..91c1da9a
--- /dev/null
+++ b/docs/java/jvm/[加餐]大白话带你认识JVM.md
@@ -0,0 +1,488 @@
+> 来自掘金用户:[说出你的愿望吧丷](https://juejin.im/user/5c2400afe51d45451758aa96)投稿,原文地址:https://juejin.im/post/5e1505d0f265da5d5d744050#heading-28
+
+## 前言
+
+如果在文中用词或者理解方面出现问题,欢迎指出。此文旨在提及和而不深究,但会尽量效率地把知识点都抛出来
+
+## 一、JVM的基本介绍
+
+JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。通过在实际的计算机上仿真模拟各类计算机功能实现···
+
+好,其实抛开这么专业的句子不说,就知道JVM其实就类似于一台小电脑运行在windows或者linux这些操作系统环境下即可。它直接和操作系统进行交互,与硬件不直接交互,可操作系统可以帮我们完成和硬件进行交互的工作。
+
+
+### 1.1 Java文件是如何被运行的
+
+比如我们现在写了一个 HelloWorld.java 好了,那这个 HelloWorld.java 抛开所有东西不谈,那是不是就类似于一个文本文件,只是这个文本文件它写的都是英文,而且有一定的缩进而已。
+
+那我们的 **JVM** 是不认识文本文件的,所以它需要一个 **编译** ,让其成为一个它会读二进制文件的 **HelloWorld.class**
+
+#### ① 类加载器
+
+如果 **JVM** 想要执行这个 **.class** 文件,我们需要将其装进一个 **类加载器** 中,它就像一个搬运工一样,会把所有的 **.class** 文件全部搬进JVM里面来。
+
+
+#### ② 方法区
+
+**方法区** 是用于存放类似于元数据信息方面的数据的,比如类信息,常量,静态变量,编译后代码···等
+
+类加载器将 .class 文件搬过来就是先丢到这一块上
+
+#### ③ 堆
+
+**堆** 主要放了一些存储的数据,比如对象实例,数组···等,它和方法区都同属于 **线程共享区域** 。也就是说它们都是 **线程不安全** 的
+
+#### ④ 栈
+
+**栈** 这是我们的代码运行空间。我们编写的每一个方法都会放到 **栈** 里面运行。
+
+我们会听说过 本地方法栈 或者 本地方法接口 这两个名词,不过我们基本不会涉及这两块的内容,它俩底层是使用C来进行工作的,和Java没有太大的关系。
+
+#### ⑤ 程序计数器
+
+主要就是完成一个加载工作,类似于一个指针一样的,指向下一行我们需要执行的代码。和栈一样,都是 **线程独享** 的,就是说每一个线程都会有自己对应的一块区域而不会存在并发和多线程的问题。
+
+
+#### 小总结
+
+1. Java文件经过编译后变成 .class 字节码文件
+2. 字节码文件通过类加载器被搬运到 JVM 虚拟机中
+3. 虚拟机主要的5大块:方法区,堆都为线程共享区域,有线程安全问题,栈和本地方法栈和计数器都是独享区域,不存在线程安全问题,而 JVM 的调优主要就是围绕堆,栈两大块进行
+
+### 1.2 简单的代码例子
+
+一个简单的学生类
+
+
+一个main方法
+
+
+执行main方法的步骤如下:
+
+1. 编译好 App.java 后得到 App.class 后,执行 App.class,系统会启动一个 JVM 进程,从 classpath 路径中找到一个名为 App.class 的二进制文件,将 App 的类信息加载到运行时数据区的方法区内,这个过程叫做 App 类的加载
+2. JVM 找到 App 的主程序入口,执行main方法
+3. 这个main中的第一条语句为 Student student = new Student("tellUrDream") ,就是让 JVM 创建一个Student对象,但是这个时候方法区中是没有 Student 类的信息的,所以 JVM 马上加载 Student 类,把 Student 类的信息放到方法区中
+4. 加载完 Student 类后,JVM 在堆中为一个新的 Student 实例分配内存,然后调用构造函数初始化 Student 实例,这个 Student 实例持有 **指向方法区中的 Student 类的类型信息** 的引用
+5. 执行student.sayName();时,JVM 根据 student 的引用找到 student 对象,然后根据 student 对象持有的引用定位到方法区中 student 类的类型信息的方法表,获得 sayName() 的字节码地址。
+6. 执行sayName()
+
+其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。
+
+## 二、类加载器的介绍
+
+之前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine 来决定
+
+### 2.1 类加载器的流程
+
+从类被加载到虚拟机内存中开始,到释放内存总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中**验证,准备,解析三个部分统称为连接**
+
+#### 2.1.1 加载
+
+1. 将class文件加载到内存
+2. 将静态数据结构转化成方法区中运行时的数据结构
+3. 在堆中生成一个代表这个类的 java.lang.Class对象作为数据访问的入口
+
+#### 2.1.2 链接
+
+1. 验证:确保加载的类符合 JVM 规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的事件,其实就是一个安全检查
+2. 准备:为static变量在方法区中分配内存空间,设置变量的初始值,例如 static int a = 3 (注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是对象初始化时赋值的)
+3. 解析:虚拟机将常量池内的符号引用替换为直接引用的过程(符号引用比如我现在import java.util.ArrayList这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)
+
+#### 2.1.3 初始化
+
+初始化其实就是一个赋值的操作,它会执行一个类构造器的<clinit>()方法。由编译器自动收集类中所有变量的赋值动作,此时准备阶段时的那个 static int a = 3 的例子,在这个时候就正式赋值为3
+
+#### 2.1.4 卸载
+
+GC将无用对象从内存中卸载
+
+### 2.2 类加载器的加载顺序
+
+加载一个Class类的顺序也是有优先级的,类加载器从最底层开始往上的顺序是这样的
+
+1. BootStrap ClassLoader:rt.jar
+2. Extention ClassLoader: 加载扩展的jar包
+3. App ClassLoader:指定的classpath下面的jar包
+4. Custom ClassLoader:自定义的类加载器
+
+### 2.3 双亲委派机制
+
+当一个类收到了加载请求时,它是不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要new一个Person,这个Person是我们自定义的类,如果我们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的Class)时,子类加载器才会自行尝试加载
+
+这样做的好处是,加载位于rt.jar包中的类时不管是哪个加载器加载,最终都会委托到BootStrap ClassLoader进行加载,这样保证了使用不同的类加载器得到的都是同一个结果。
+
+其实这个也是一个隔离的作用,避免了我们的代码影响了JDK的代码,比如我现在要来一个
+
+ public class String(){
+ public static void main(){sout;}
+ }
+
+这种时候,我们的代码肯定会报错,因为在加载的时候其实是找到了rt.jar中的String.class,然后发现这也没有main方法
+
+## 三、运行时数据区
+
+### 3.1 本地方法栈和程序计数器
+
+比如说我们现在点开Thread类的源码,会看到它的start0方法带有一个native关键字修饰,而且不存在方法体,这种用native修饰的方法就是本地方法,这是使用C来实现的,然后一般这些方法都会放到一个叫做本地方法栈的区域。
+
+程序计数器其实就是一个指针,它指向了我们程序中下一句需要执行的指令,它也是内存区域中唯一一个不会出现OutOfMemoryError的区域,而且占用内存空间小到基本可以忽略不计。这个内存仅代表当前线程所执行的字节码的行号指示器,字节码解析器通过改变这个计数器的值选取下一条需要执行的字节码指令。
+
+如果执行的是native方法,那这个指针就不工作了。
+
+### 3.2 方法区
+
+方法区主要的作用技术存放类的元数据信息,常量和静态变量···等。当它存储的信息过大时,会在无法满足内存分配时报错。
+
+
+### 3.3 虚拟机栈和虚拟机堆
+
+一句话便是:栈管运行,堆管存储。则虚拟机栈负责运行代码,而虚拟机堆负责存储数据。
+
+#### 3.3.1 虚拟机栈的概念
+
+它是Java方法执行的内存模型。里面会对局部变量,动态链表,方法出口,栈的操作(入栈和出栈)进行存储,且线程独享。同时如果我们听到局部变量表,那也是在说虚拟机栈
+
+ public class Person{
+ int a = 1;
+
+ public void doSomething(){
+ int b = 2;
+ }
+ }
+
+
+#### 3.3.2 虚拟机栈存在的异常
+
+如果线程请求的栈的深度大于虚拟机栈的最大深度,就会报 **StackOverflowError** (这种错误经常出现在递归中)。Java虚拟机也可以动态扩展,但随着扩展会不断地申请内存,当无法申请足够内存时就会报错 **OutOfMemoryError**。
+
+#### 3.3.3 虚拟机栈的生命周期
+
+对于栈来说,不存在垃圾回收。只要程序运行结束,栈的空间自然就会释放了。栈的生命周期和所处的线程是一致的。
+
+这里补充一句:8种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。
+
+#### 3.3.4 虚拟机栈的执行
+
+我们经常说的栈帧数据,说白了在JVM中叫栈帧,放到Java中其实就是方法,它也是存放在栈中的。
+
+栈中的数据都是以栈帧的格式存在,它是一个关于方法和运行期数据的数据集。比如我们执行一个方法a,就会对应产生一个栈帧A1,然后A1会被压入栈中。同理方法b会有一个B1,方法c会有一个C1,等到这个线程执行完毕后,栈会先弹出C1,后B1,A1。它是一个先进后出,后进先出原则。
+
+#### 3.3.5 局部变量的复用
+
+局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以Slot为最小单位,一个slot可以存放32位以内的数据类型。
+
+虚拟机通过索引定位的方式使用局部变量表,范围为[0,局部变量表的slot的数量]。方法中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而为了节省栈帧空间,这些slot是可以复用的,当方法执行位置超过了某个变量,那么这个变量的slot可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动这些内存。
+
+#### 3.3.6 虚拟机堆的概念
+
+JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为**年轻代**和**老年代**,而非堆内存则为**永久代**。年轻代又会分为**Eden**和**Survivor**区。Survivor也会分为**FromPlace**和**ToPlace**,toPlace的survivor区域是空的。Eden,FromPlace和ToPlace的默认占比为 **8:1:1**。当然这个东西其实也可以通过一个 -XX:+UsePSAdaptiveSurvivorSizePolicy 参数来根据生成对象的速率动态调整
+
+堆内存中存放的是对象,垃圾收集就是收集这些对象然后交给GC算法进行回收。非堆内存其实我们已经说过了,就是方法区。在1.8中已经移除永久代,替代品是一个元空间(MetaSpace),最大区别是metaSpace是不存在于JVM中的,它使用的是本地内存。并有两个参数
+
+ MetaspaceSize:初始化元空间大小,控制发生GC
+ MaxMetaspaceSize:限制元空间大小上限,防止占用过多物理内存。
+
+移除的原因可以大致了解一下:融合HotSpot JVM和JRockit VM而做出的改变,因为JRockit是没有永久代的,不过这也间接性地解决了永久代的OOM问题。
+
+#### 3.3.7 Eden年轻代的介绍
+
+当我们new一个对象后,会先放到Eden划分出来的一块作为存储空间的内存,但是我们知道对堆内存是线程共享的,所以有可能会出现两个对象共用一个内存的情况。这里JVM的处理是每个线程都会预先申请好一块连续的内存空间并规定了对象存放的位置,而如果空间不足会再申请多块内存空间。这个操作我们会称作TLAB,有兴趣可以了解一下。
+
+当Eden空间满了之后,会触发一个叫做Minor GC(就是一个发生在年轻代的GC)的操作,存活下来的对象移动到Survivor0区。Survivor0区满后触发 Minor GC,就会将存活对象移动到Survivor1区,此时还会把from和to两个指针交换,这样保证了一段时间内总有一个survivor区为空且to所指向的survivor区为空。经过多次的 Minor GC后仍然存活的对象(**这里的存活判断是15次,对应到虚拟机参数为 -XX:MaxTenuringThreshold 。为什么是15,因为HotSpot会在对象投中的标记字段里记录年龄,分配到的空间仅有4位,所以最多只能记录到15**)会移动到老年代。老年代是存储长期存活的对象的,占满时就会触发我们最常听说的Full GC,期间会停止所有线程等待GC的完成。所以对于响应要求高的应用应该尽量去减少发生Full GC从而避免响应超时的问题。
+
+而且当老年区执行了full gc之后仍然无法进行对象保存的操作,就会产生OOM,这时候就是虚拟机中的堆内存不足,原因可能会是堆内存设置的大小过小,这个可以通过参数-Xms、-Xms来调整。也可能是代码中创建的对象大且多,而且它们一直在被引用从而长时间垃圾收集无法收集它们。
+
+
+
+补充说明:关于-XX:TargetSurvivorRatio参数的问题。其实也不一定是要满足-XX:MaxTenuringThreshold才移动到老年代。可以举个例子:如对象年龄5的占30%,年龄6的占36%,年龄7的占34%,加入某个年龄段(如例子中的年龄6)后,总占用超过Survivor空间*TargetSurvivorRatio的时候,从该年龄段开始及大于的年龄对象就要进入老年代(即例子中的年龄6对象,就是年龄6和年龄7晋升到老年代),这时候无需等到MaxTenuringThreshold中要求的15
+
+#### 3.3.8 如何判断一个对象需要被干掉
+
+
+
+图中程序计数器、虚拟机栈、本地方法栈,3个区域随着线程的生存而生存的。内存分配和回收都是确定的。随着线程的结束内存自然就被回收了,因此不需要考虑垃圾回收的问题。而Java堆和方法区则不一样,各线程共享,内存的分配和回收都是动态的。因此垃圾收集器所关注的都是堆和方法这部分内存。
+
+在进行回收前就要判断哪些对象还存活,哪些已经死去。下面介绍两个基础的计算方法
+
+1.引用计数器计算:给对象添加一个引用计数器,每次引用这个对象时计数器加一,引用失效时减一,计数器等于0时就是不会再次使用的。不过这个方法有一种情况就是出现对象的循环引用时GC没法回收。
+
+2.可达性分析计算:这是一种类似于二叉树的实现,将一系列的GC ROOTS作为起始的存活对象集,从这个节点往下搜索,搜索所走过的路径成为引用链,把能被该集合引用到的对象加入到集合中。搜索当一个对象到GC Roots没有使用任何引用链时,则说明该对象是不可用的。主流的商用程序语言,例如Java,C#等都是靠这招去判定对象是否存活的。
+
+(了解一下即可)在Java语言汇总能作为GC Roots的对象分为以下几种:
+
+1. 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
+2. 方法区中静态变量所引用的对象(静态变量)
+3. 方法区中常量引用的对象
+4. 本地方法栈(即native修饰的方法)中JNI引用的对象(JNI是Java虚拟机调用对应的C函数的方式,通过JNI函数也可以创建新的Java对象。且JNI对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
+5. 已启动的且未终止的Java线程
+
+
+这种方法的优点是能够解决循环引用的问题,可它的实现需要耗费大量资源和时间,也需要GC(它的分析过程引用关系不能发生变化,所以需要停止所有进程)
+
+#### 3.3.9 如何宣告一个对象的真正死亡
+
+首先必须要提到的是一个名叫 **finalize()** 的方法
+
+finalize()是Object类的一个方法、一个对象的finalize()方法只会被系统自动调用一次,经过finalize()方法逃脱死亡的对象,第二次不会再调用。
+
+补充一句:并不提倡在程序中调用finalize()来进行自救。建议忘掉Java程序中该方法的存在。因为它执行的时间不确定,甚至是否被执行也不确定(Java程序的不正常退出),而且运行代价高昂,无法保证各个对象的调用顺序(甚至有不同线程中调用)。在Java9中已经被标记为 **deprecated** ,且java.lang.ref.Cleaner(也就是强、软、弱、幻象引用的那一套)中已经逐步替换掉它,会比finalize来的更加的轻量及可靠。
+
+
+
+判断一个对象的死亡至少需要两次标记
+
+1. 如果对象进行可达性分析之后没发现与GC Roots相连的引用链,那它将会第一次标记并且进行一次筛选。判断的条件是决定这个对象是否有必要执行finalize()方法。如果对象有必要执行finalize()方法,则被放入F-Queue队列中。
+2. GC对F-Queue队列中的对象进行二次标记。如果对象在finalize()方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出“即将回收”集合。如果此时对象还没成功逃脱,那么只能被回收了。
+
+如果确定对象已经死亡,我们又该如何回收这些垃圾呢
+
+### 3.4 垃圾回收算法
+
+不会非常详细的展开,常用的有标记清除,复制,标记整理和分代收集算法
+
+#### 3.4.1 标记清除算法
+
+标记清除算法就是分为“标记”和“清除”两个阶段。标记出所有需要回收的对象,标记结束后统一回收。这个套路很简单,也存在不足,后续的算法都是根据这个基础来加以改进的。
+
+其实它就是把已死亡的对象标记为空闲内存,然后记录在一个空闲列表中,当我们需要new一个对象时,内存管理模块会从空闲列表中寻找空闲的内存来分给新的对象。
+
+不足的方面就是标记和清除的效率比较低下。且这种做法会让内存中的碎片非常多。这个导致了如果我们需要使用到较大的内存块时,无法分配到足够的连续内存。比如下图
+
+
+
+此时可使用的内存块都是零零散散的,导致了刚刚提到的大内存对象问题
+
+#### 3.4.2 复制算法
+
+为了解决效率问题,复制算法就出现了。它将可用内存按容量划分成两等分,每次只使用其中的一块。和survivor一样也是用from和to两个指针这样的玩法。fromPlace存满了,就把存活的对象copy到另一块toPlace上,然后交换指针的内容。这样就解决了碎片的问题。
+
+这个算法的代价就是把内存缩水了,这样堆内存的使用效率就会变得十分低下了
+
+
+
+不过它们分配的时候也不是按照1:1这样进行分配的,就类似于Eden和Survivor也不是等价分配是一个道理。
+
+#### 3.4.3 标记整理算法
+
+复制算法在对象存活率高的时候会有一定的效率问题,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存
+
+
+
+#### 3.4.4 分代收集算法
+
+这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。
+
+说白了就是八仙过海各显神通,具体问题具体分析了而已。
+
+### 3.5 (了解)各种各样的垃圾回收器
+
+HotSpot VM中的垃圾回收器,以及适用场景
+
+
+到jdk8为止,默认的垃圾收集器是Parallel Scavenge 和 Parallel Old
+
+从jdk9开始,G1收集器成为默认的垃圾收集器
+目前来看,G1回收器停顿时间最短而且没有明显缺点,非常适合Web应用。在jdk8中测试Web应用,堆内存6G,新生代4.5G的情况下,Parallel Scavenge 回收新生代停顿长达1.5秒。G1回收器回收同样大小的新生代只停顿0.2秒。
+
+### 3.6 (了解)JVM的常用参数
+
+JVM的参数非常之多,这里只列举比较重要的几个,通过各种各样的搜索引擎也可以得知这些信息。
+
+| 参数名称 | 含义 | 默认值 | 说明 |
+|------|------------|------------|------|
+| -Xms | 初始堆大小 | 物理内存的1/64(<1GB) |默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
+| -Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
+| -Xmn | 年轻代大小(1.4or lator) | |注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
+| -XX:NewSize | 设置年轻代大小(for 1.3/1.4) | |
+| -XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | |
+| -XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 |
+| -XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 |
+| -Xss | 每个线程的堆栈大小 | | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了
+| -XX:NewRatio | 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) | |-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
+| -XX:SurvivorRatio | Eden区与Survivor区的大小比值 | |设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
+| -XX:+DisableExplicitGC | 关闭System.gc() | |这个参数需要严格的测试
+| -XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 |单位字节 新生代采用Parallel ScavengeGC时无效另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象.
+| -XX:ParallelGCThreads | 并行收集器的线程数 | |此值最好配置与处理器数目相等 同样适用于CMS
+| -XX:MaxGCPauseMillis | 每次年轻代垃圾回收的最长时间(最大暂停时间) | |如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.
+
+其实还有一些打印及CMS方面的参数,这里就不以一一列举了
+
+## 四、关于JVM调优的一些方面
+
+根据刚刚涉及的jvm的知识点,我们可以尝试对JVM进行调优,主要就是堆内存那块
+
+所有线程共享数据区大小=新生代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m。所以java堆中增大年轻代后,将会减小年老代大小(因为老年代的清理是使用fullgc,所以老年代过小的话反而是会增多fullgc的)。此值对系统性能影响较大,Sun官方推荐配置为java堆的3/8。
+
+### 4.1 调整最大堆内存和最小堆内存
+
+-Xmx –Xms:指定java堆最大值(默认值是物理内存的1/4(<1GB))和初始java堆最小值(默认值是物理内存的1/64(<1GB))
+
+默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。简单点来说,你不停地往堆内存里面丢数据,等它剩余大小小于40%了,JVM就会动态申请内存空间不过会小于-Xmx,如果剩余大小大于70%,又会动态缩小不过不会小于–Xms。就这么简单
+
+开发过程中,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。
+
+我们执行下面的代码
+
+ System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
+ System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
+ System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
+
+注意:此处设置的是Java堆大小,也就是新生代大小 + 老年代大小
+
+
+设置一个VM options的参数
+
+ -Xmx20m -Xms5m -XX:+PrintGCDetails
+
+
+
+再次启动main方法
+
+
+这里GC弹出了一个Allocation Failure分配失败,这个事情发生在PSYoungGen,也就是年轻代中
+
+这时候申请到的内存为18M,空闲内存为4.214195251464844M
+
+我们此时创建一个字节数组看看,执行下面的代码
+
+ byte[] b = new byte[1 * 1024 * 1024];
+ System.out.println("分配了1M空间给数组");
+ System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
+ System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
+ System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
+
+
+
+
+此时free memory就又缩水了,不过total memory是没有变化的。Java会尽可能将total mem的值维持在最小堆内存大小
+
+
+ byte[] b = new byte[10 * 1024 * 1024];
+ System.out.println("分配了10M空间给数组");
+ System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
+ System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
+ System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
+
+
+
+这时候我们创建了一个10M的字节数据,这时候最小堆内存是顶不住的。我们会发现现在的total memory已经变成了15M,这就是已经申请了一次内存的结果。
+
+此时我们再跑一下这个代码
+
+ System.gc();
+ System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); //系统的最大空间
+ System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); //系统的空闲空间
+ System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); //当前可用的总空间
+
+
+
+
+此时我们手动执行了一次fullgc,此时total memory的内存空间又变回5.5M了,此时又是把申请的内存释放掉的结果。
+
+### 4.2 调整新生代和老年代的比值
+
+-XX:NewRatio --- 新生代(eden+2*Survivor)和老年代(不包含永久区)的比值
+
+例如:-XX:NewRatio=4,表示新生代:老年代=1:4,即新生代占整个堆的1/5。在Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
+
+### 4.3 调整Survivor区和Eden区的比值
+
+-XX:SurvivorRatio(幸存代)--- 设置两个Survivor区和eden的比值
+
+例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10
+
+### 4.4 设置年轻代和老年代的大小
+
+-XX:NewSize --- 设置年轻代大小
+
+-XX:MaxNewSize --- 设置年轻代最大值
+
+可以通过设置不同参数来测试不同的情况,反正最优解当然就是官方的Eden和Survivor的占比为8:1:1,然后在刚刚介绍这些参数的时候都已经附带了一些说明,感兴趣的也可以看看。反正最大堆内存和最小堆内存如果数值不同会导致多次的gc,需要注意。
+
+### 4.5 小总结
+
+根据实际事情调整新生代和幸存代的大小,官方推荐新生代占java堆的3/8,幸存代占新生代的1/10
+
+在OOM时,记得Dump出堆,确保可以排查现场问题,通过下面命令你可以输出一个.dump文件,这个文件可以使用VisualVM或者Java自带的Java VisualVM工具。
+
+ -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径
+
+一般我们也可以通过编写脚本的方式来让OOM出现时给我们报个信,可以通过发送邮件或者重启程序等来解决。
+
+### 4.6 永久区的设置
+
+ -XX:PermSize -XX:MaxPermSize
+
+初始空间(默认为物理内存的1/64)和最大空间(默认为物理内存的1/4)。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。
+
+tips:如果堆空间没有用完也抛出了OOM,有可能是永久区导致的。堆空间实际占用非常少,但是永久区溢出 一样抛出OOM。
+
+### 4.7 JVM的栈参数调优
+
+#### 4.7.1 调整每个线程栈空间的大小
+
+可以通过-Xss:调整每个线程栈空间的大小
+
+JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
+
+#### 4.7.2 设置线程栈的大小
+ -XXThreadStackSize:
+ 设置线程栈的大小(0 means use default stack size)
+
+这些参数都是可以通过自己编写程序去简单测试的,这里碍于篇幅问题就不再提供demo了
+
+### 4.8 (可以直接跳过了)JVM其他参数介绍
+
+形形色色的参数很多,就不会说把所有都扯个遍了,因为大家其实也不会说一定要去深究到底。
+
+#### 4.8.1 设置内存页的大小
+
+ -XXThreadStackSize:
+ 设置内存页的大小,不可设置过大,会影响Perm的大小
+
+#### 4.8.2 设置原始类型的快速优化
+
+ -XX:+UseFastAccessorMethods:
+ 设置原始类型的快速优化
+
+#### 4.8.3 设置关闭手动GC
+ -XX:+DisableExplicitGC:
+ 设置关闭System.gc()(这个参数需要严格的测试)
+
+#### 4.8.4 设置垃圾最大年龄
+ -XX:MaxTenuringThreshold
+ 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代.
+ 对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,
+ 则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,
+ 增加在年轻代即被回收的概率。该参数只有在串行GC时才有效.
+
+#### 4.8.5 加快编译速度
+ -XX:+AggressiveOpts
+加快编译速度
+
+#### 4.8.6 改善锁机制性能
+ -XX:+UseBiasedLocking
+
+#### 4.8.7 禁用垃圾回收
+ -Xnoclassgc
+
+#### 4.8.8 设置堆空间存活时间
+ -XX:SoftRefLRUPolicyMSPerMB
+ 设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s。
+
+#### 4.8.9 设置对象直接分配在老年代
+ -XX:PretenureSizeThreshold
+ 设置对象超过多大时直接在老年代分配,默认值是0。
+
+#### 4.8.10 设置TLAB占eden区的比例
+ -XX:TLABWasteTargetPercent
+ 设置TLAB占eden区的百分比,默认值是1% 。
+
+#### 4.8.11设置是否优先YGC
+ -XX:+CollectGen0First
+ 设置FullGC时是否先YGC,默认值是false。
+
+
+## finally
+
+真的扯了很久这东西,参考了多方的资料,有极客时间的《深入拆解虚拟机》和《Java核心技术面试精讲》,也有百度,也有自己在学习的一些线上课程的总结。希望对你有所帮助,谢谢。
diff --git a/docs/java/jvm/jvm 知识点汇总.md b/docs/java/jvm/jvm 知识点汇总.md
new file mode 100644
index 00000000..4529e858
--- /dev/null
+++ b/docs/java/jvm/jvm 知识点汇总.md
@@ -0,0 +1,13 @@
+
+无论什么级别的Java从业者,JVM都是进阶时必须迈过的坎。不管是工作还是面试中,JVM都是必考题。如果不懂JVM的话,薪酬会非常吃亏(近70%的面试者挂在JVM上了)。
+
+
+掌握了JVM机制,就等于学会了深层次解决问题的方法。对于Java开发者而言,只有熟悉底层虚拟机的运行机制,才能通过JVM日志深入到字节码的层次去分析排查问题,发现隐性的系统缺陷,进而提升系统性能。
+
+
+一些技术人员开发工具用得很熟练,触及JVM问题时却是模棱两可,甚至连内存模型和内存区域,HotSpot和JVM规范,都混淆不清。工作很长时间,在生产时还在用缺省参数来直接启动,以致系统运行时出现性能、稳定性等问题时束手无措,不知该如何追踪排查。久而久之,这对自己的职业成长是极为不利的。
+
+
+掌握JVM,是深入Java技术栈的必经之路。
+
+
diff --git a/docs/java/jvm/最重要的JVM参数指南.md b/docs/java/jvm/最重要的JVM参数指南.md
new file mode 100644
index 00000000..463870fd
--- /dev/null
+++ b/docs/java/jvm/最重要的JVM参数指南.md
@@ -0,0 +1,137 @@
+> 本文由 JavaGuide 翻译自 https://www.baeldung.com/jvm-parameters,并对文章进行了大量的完善补充。翻译不易,如需转载请注明出处为: 作者: 。
+
+## 1.概述
+
+在本篇文章中,你将掌握最常用的 JVM 参数配置。如果对于下面提到了一些概念比如堆、
+
+## 2.堆内存相关
+
+>Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
+>
+
+### 2.1.显式指定堆内存`–Xms`和`-Xmx`
+
+与性能有关的最常见实践之一是根据应用程序要求初始化堆内存。如果我们需要指定最小和最大堆大小(推荐显示指定大小),以下参数可以帮助你实现:
+
+```
+-Xms[unit]
+-Xmx[unit]
+```
+
+- **heap size** 表示要初始化内存的具体大小。
+- **unit** 表示要初始化内存的单位。单位为***“ g”*** (GB) 、***“ m”***(MB)、***“ k”***(KB)。
+
+举个栗子🌰,如果我们要为JVM分配最小2 GB和最大5 GB的堆内存大小,我们的参数应该这样来写:
+
+```
+-Xms2G -Xmx5G
+```
+
+### 2.2.显式新生代内存(Young Ceneration)
+
+根据[Oracle官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/sizing.html),在堆总可用内存配置完成之后,第二大影响因素是为 `Young Generation` 在堆内存所占的比例。默认情况下,YG 的最小大小为 1310 *MB*,最大大小为*无限制*。
+
+一共有两种指定 新生代内存(Young Ceneration)大小的方法:
+
+**1.通过`-XX:NewSize`和`-XX:MaxNewSize`指定**
+
+```
+-XX:NewSize=[unit]
+-XX:MaxNewSize=[unit]
+```
+
+举个栗子🌰,如果我们要为 新生代分配 最小256m 的内存,最大 1024m的内存我们的参数应该这样来写:
+
+```
+-XX:NewSize=256m
+-XX:MaxNewSize=1024m
+```
+
+**2.通过`-Xmn[unit] `指定**
+
+举个栗子🌰,如果我们要为 新生代分配256m的内存(NewSize与MaxNewSize设为一致),我们的参数应该这样来写:
+
+```
+-Xmn256m
+```
+
+GC 调优策略中很重要的一条经验总结是这样说的:
+
+> 将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
+
+另外,你还可以通过**`-XX:NewRatio=`**来设置新生代和老年代内存的比值。
+
+比如下面的参数就是设置新生代(包括Eden和两个Survivor区)与老年代的比值为1。也就是说:新生代与老年代所占比值为1:1,新生代占整个堆栈的 1/2。
+
+```
+-XX:NewRatio=1
+```
+
+### 2.3.显示指定永久代/元空间的大小
+
+**从Java 8开始,如果我们没有指定 Metaspace 的大小,随着更多类的创建,虚拟机会耗尽所有可用的系统内存(永久代并不会出现这种情况)。**
+
+JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
+
+```java
+-XX:PermSize=N //方法区 (永久代) 初始大小
+-XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
+```
+
+相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
+
+**JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。**
+
+下面是一些常用参数:
+
+```java
+-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
+-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
+```
+
+## 3.垃圾收集相关
+
+### 3.1.垃圾回收器
+
+为了提高应用程序的稳定性,选择正确的[垃圾收集](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html)算法至关重要。
+
+JVM具有四种类型的*GC*实现:
+
+- 串行垃圾收集器
+- 并行垃圾收集器
+- CMS垃圾收集器
+- G1垃圾收集器
+
+可以使用以下参数声明这些实现:
+
+```
+-XX:+UseSerialGC
+-XX:+UseParallelGC
+-XX:+USeParNewGC
+-XX:+UseG1GC
+```
+
+有关*垃圾回收*实施的更多详细信息,请参见[此处](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md)。
+
+### 3.2.GC记录
+
+为了严格监控应用程序的运行状况,我们应该始终检查JVM的*垃圾回收*性能。最简单的方法是以人类可读的格式记录*GC*活动。
+
+使用以下参数,我们可以记录*GC*活动:
+
+```
+-XX:+UseGCLogFileRotation
+-XX:NumberOfGCLogFiles=< number of log files >
+-XX:GCLogFileSize=< file size >[ unit ]
+-Xloggc:/path/to/gc.log
+```
+
+
+
+## 推荐阅读
+
+- [CMS GC 默认新生代是多大?](https://www.jianshu.com/p/832fc4d4cb53)
+- [CMS GC启动参数优化配置](https://www.cnblogs.com/hongdada/p/10277782.html)
+- [从实际案例聊聊Java应用的GC优化-美团技术团队](https://tech.meituan.com/2017/12/29/jvm-optimize.html)
+- [JVM性能调优详解](https://www.choupangxia.com/2019/11/11/interview-jvm-gc-08/) (2019-11-11)
+- [JVM参数使用手册](https://segmentfault.com/a/1190000010603813)
\ No newline at end of file
diff --git a/docs/java/jvm/类加载器.md b/docs/java/jvm/类加载器.md
new file mode 100644
index 00000000..00a89047
--- /dev/null
+++ b/docs/java/jvm/类加载器.md
@@ -0,0 +1,142 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [回顾一下类加载过程](#回顾一下类加载过程)
+- [类加载器总结](#类加载器总结)
+- [双亲委派模型](#双亲委派模型)
+ - [双亲委派模型介绍](#双亲委派模型介绍)
+ - [双亲委派模型实现源码分析](#双亲委派模型实现源码分析)
+ - [双亲委派模型的好处](#双亲委派模型的好处)
+ - [如果我们不想要双亲委派模型怎么办?](#如果我们不想要双亲委派模型怎么办)
+- [自定义类加载器](#自定义类加载器)
+- [推荐](#推荐)
+
+
+
+> 公众号JavaGuide 后台回复关键字“1”,免费获取JavaGuide配套的Java工程师必备学习资源(文末有公众号二维码)。
+
+## 回顾一下类加载过程
+
+类加载过程:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。
+
+
+
+一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。
+
+所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。
+
+## 类加载器总结
+
+JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`:
+
+1. **BootstrapClassLoader(启动类加载器)** :最顶层的加载类,由C++实现,负责加载 `%JAVA_HOME%/lib`目录下的jar包和类或者或被 `-Xbootclasspath`参数指定的路径中的所有类。
+2. **ExtensionClassLoader(扩展类加载器)** :主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。
+3. **AppClassLoader(应用程序类加载器)** :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
+
+## 双亲委派模型
+
+### 双亲委派模型介绍
+
+每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。
+
+
+
+每个类加载都有一个父类加载器,我们通过下面的程序来验证。
+
+```java
+public class ClassLoaderDemo {
+ public static void main(String[] args) {
+ System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
+ System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
+ System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
+ }
+}
+```
+
+Output
+
+```
+ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
+The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586
+The GrandParent of ClassLodarDemo's ClassLoader is null
+```
+
+`AppClassLoader`的父类加载器为`ExtClassLoader`
+`ExtClassLoader`的父类加载器为null,**null并不代表`ExtClassLoader`没有父类加载器,而是 `BootstrapClassLoader`** 。
+
+其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。官方API文档对这部分的描述如下:
+
+>The Java platform uses a delegation model for loading classes. **The basic idea is that every class loader has a "parent" class loader.** 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.
+
+### 双亲委派模型实现源码分析
+
+双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 `java.lang.ClassLoader` 的 `loadClass()` 中,相关代码如下所示。
+
+```java
+private final ClassLoader parent;
+protected Class> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (getClassLoadingLock(name)) {
+ // 首先,检查请求的类是否已经被加载过
+ Class> c = findLoadedClass(name);
+ if (c == null) {
+ long t0 = System.nanoTime();
+ try {
+ if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
+ c = parent.loadClass(name, false);
+ } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
+ c = findBootstrapClassOrNull(name);
+ }
+ } catch (ClassNotFoundException e) {
+ //抛出异常说明父类加载器无法完成加载请求
+ }
+
+ if (c == null) {
+ long t1 = System.nanoTime();
+ //自己尝试加载
+ c = findClass(name);
+
+ // this is the defining class loader; record the stats
+ sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
+ sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
+ sun.misc.PerfCounter.getFindClasses().increment();
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+```
+
+### 双亲委派模型的好处
+
+双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。
+
+### 如果我们不想用双亲委派模型怎么办?
+
+为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 `loadClass()` 即可。
+
+## 自定义类加载器
+
+除了 `BootstrapClassLoader` 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`。如果我们要自定义自己的类加载器,很明显需要继承 `ClassLoader`。
+
+## 推荐阅读
+
+-
+-
+-
+
+### 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
+
diff --git a/docs/java/jvm/类加载过程.md b/docs/java/jvm/类加载过程.md
new file mode 100644
index 00000000..9330c581
--- /dev/null
+++ b/docs/java/jvm/类加载过程.md
@@ -0,0 +1,124 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+> 公众号JavaGuide 后台回复关键字“1”,免费获取JavaGuide配套的Java工程师必备学习资源(文末有公众号二维码)。
+
+
+
+- [类的生命周期](#类的生命周期)
+ - [类加载过程](#类加载过程)
+ - [加载](#加载)
+ - [验证](#验证)
+ - [准备](#准备)
+ - [解析](#解析)
+ - [初始化](#初始化)
+ - [卸载](#卸载)
+ - [公众号](#公众号)
+
+
+
+# 类的生命周期
+
+一个类的完整生命周期如下:
+
+
+
+
+## 类加载过程
+
+Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
+
+系统加载 Class 类型的文件主要三步:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。
+
+
+
+### 加载
+
+类加载过程的第一步,主要完成下面3件事情:
+
+1. 通过全类名获取定义此类的二进制字节流
+2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
+3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口
+
+虚拟机规范多上面这3点并不具体,因此是非常灵活的。比如:"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取(日后出现的JAR、EAR、WAR格式的基础)、其他文件生成(典型应用就是JSP)等等。
+
+**一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。**
+
+类加载器、双亲委派模型也是非常重要的知识点,这部分内容会在后面的文章中单独介绍到。
+
+加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
+
+### 验证
+
+
+
+### 准备
+
+**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
+
+1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
+2. 这里所设置的初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 fianl 关键字`public static final int value=111` ,那么准备阶段 value 的值就被赋值为 111。
+
+**基本数据类型的零值:**
+
+
+
+### 解析
+
+解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
+
+符号引用就是一组符号来描述目标,可以是任何字面量。**直接引用**就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
+
+综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
+
+### 初始化
+
+初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 ` ()`方法的过程。
+
+对于`()` 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 `()` 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
+
+对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):
+
+1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
+ - 当jvm执行new指令时会初始化类。即当程序创建一个类的实例对象。
+ - 当jvm执行getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
+ - 当jvm执行putstatic指令时会初始化类。即程序给类的静态变量赋值。
+ - 当jvm执行invokestatic指令时会初始化类。即程序调用类的静态方法。
+2. 使用 `java.lang.reflect` 包的方法对类进行反射调用时如Class.forname("..."),newInstance()等等。 ,如果类没初始化,需要触发其初始化。
+3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
+4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
+5. MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用,
+ 就必须先使用findStaticVarHandle来初始化要调用的类。
+6. **「补充,来自[issue745](https://github.com/Snailclimb/JavaGuide/issues/745)」** 当一个接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
+
+## 卸载
+
+> 卸载这部分内容来自 [issue#662](https://github.com/Snailclimb/JavaGuide/issues/662)由 **[guang19](https://github.com/guang19)** 补充完善。
+
+卸载类即该类的Class对象被GC。
+
+卸载类需要满足3个要求:
+
+1. 该类的所有的实例对象都已被GC,也就是说堆不存在该类的实例对象。
+2. 该类没有在其他任何地方被引用
+3. 该类的类加载器的实例已被GC
+
+所以,在JVM生命周期类,由jvm自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。
+
+只要想通一点就好了,jdk自带的BootstrapClassLoader,PlatformClassLoader,AppClassLoader负责加载jdk提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。
+
+**参考**
+
+- 《深入理解Java虚拟机》
+- 《实战Java虚拟机》
+-
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
+
diff --git a/docs/java/jvm/类文件结构.md b/docs/java/jvm/类文件结构.md
new file mode 100644
index 00000000..fe282352
--- /dev/null
+++ b/docs/java/jvm/类文件结构.md
@@ -0,0 +1,224 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
+
+
+- [类文件结构](#类文件结构)
+ - [一 概述](#一-概述)
+ - [二 Class 文件结构总结](#二-class-文件结构总结)
+ - [2.1 魔数](#21-魔数)
+ - [2.2 Class 文件版本](#22-class-文件版本)
+ - [2.3 常量池](#23-常量池)
+ - [2.4 访问标志](#24-访问标志)
+ - [2.5 当前类索引,父类索引与接口索引集合](#25-当前类索引父类索引与接口索引集合)
+ - [2.6 字段表集合](#26-字段表集合)
+ - [2.7 方法表集合](#27-方法表集合)
+ - [2.8 属性表集合](#28-属性表集合)
+ - [参考](#参考)
+
+
+
+# 类文件结构
+
+## 一 概述
+
+在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+Clojure(Lisp 语言的一种方言)、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了不同的语言被不同的编译器编译成`.class`文件最终运行在 Java 虚拟机之上。`.class`文件的二进制格式可以使用 [WinHex](https://www.x-ways.net/winhex/) 查看。
+
+
+
+**可以说`.class`文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。**
+
+## 二 Class 文件结构总结
+
+根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成:
+
+```java
+ClassFile {
+ u4 magic; //Class 文件的标志
+ u2 minor_version;//Class 的小版本号
+ u2 major_version;//Class 的大版本号
+ u2 constant_pool_count;//常量池的数量
+ cp_info constant_pool[constant_pool_count-1];//常量池
+ u2 access_flags;//Class 的访问标记
+ u2 this_class;//当前类
+ u2 super_class;//父类
+ u2 interfaces_count;//接口
+ u2 interfaces[interfaces_count];//一个类可以实现多个接口
+ u2 fields_count;//Class 文件的字段属性
+ field_info fields[fields_count];//一个类会可以有个字段
+ u2 methods_count;//Class 文件的方法数量
+ method_info methods[methods_count];//一个类可以有个多个方法
+ u2 attributes_count;//此类的属性表中的属性数
+ attribute_info attributes[attributes_count];//属性表集合
+}
+```
+
+下面详细介绍一下 Class 文件结构涉及到的一些组件。
+
+**Class文件字节码结构组织示意图** (之前在网上保存的,非常不错,原出处不明):
+
+
+
+### 2.1 魔数
+
+```java
+ u4 magic; //Class 文件的标志
+```
+
+每个 Class 文件的头四个字节称为魔数(Magic Number),它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。
+
+程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其它特殊的含义。
+
+### 2.2 Class 文件版本
+
+```java
+ u2 minor_version;//Class 的小版本号
+ u2 major_version;//Class 的大版本号
+```
+
+紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是**次版本号**,第七和第八是**主版本号**。
+
+高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。
+
+### 2.3 常量池
+
+```java
+ u2 constant_pool_count;//常量池的数量
+ cp_info constant_pool[constant_pool_count-1];//常量池
+```
+
+紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1(**常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”**)。
+
+常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:
+
+- 类和接口的全限定名
+- 字段的名称和描述符
+- 方法的名称和描述符
+
+常量池中每一项常量都是一个表,这14种表有一个共同的特点:**开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.**
+
+| 类型 | 标志(tag) | 描述 |
+| :------------------------------: | :---------: | :--------------------: |
+| CONSTANT_utf8_info | 1 | UTF-8编码的字符串 |
+| CONSTANT_Integer_info | 3 | 整形字面量 |
+| CONSTANT_Float_info | 4 | 浮点型字面量 |
+| CONSTANT_Long_info | 5 | 长整型字面量 |
+| CONSTANT_Double_info | 6 | 双精度浮点型字面量 |
+| CONSTANT_Class_info | 7 | 类或接口的符号引用 |
+| CONSTANT_String_info | 8 | 字符串类型字面量 |
+| CONSTANT_Fieldref_info | 9 | 字段的符号引用 |
+| CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
+| CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
+| CONSTANT_NameAndType_info | 12 | 字段或方法的符号引用 |
+| CONSTANT_MothodType_info | 16 | 标志方法类型 |
+| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
+| CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
+
+`.class` 文件可以通过`javap -v class类名` 指令来看一下其常量池中的信息(`javap -v class类名-> temp.txt` :将结果输出到 temp.txt 文件)。
+
+### 2.4 访问标志
+
+在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。
+
+类访问和属性修饰符:
+
+
+
+我们定义了一个 Employee 类
+
+```java
+package top.snailclimb.bean;
+public class Employee {
+ ...
+}
+```
+
+通过`javap -v class类名` 指令来看一下类的访问标志。
+
+
+
+### 2.5 当前类索引,父类索引与接口索引集合
+
+```java
+ u2 this_class;//当前类
+ u2 super_class;//父类
+ u2 interfaces_count;//接口
+ u2 interfaces[interfaces_count];//一个类可以实现多个接口
+```
+
+**类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 java 类都有父类,因此除了 `java.lang.Object` 外,所有 Java 类的父类索引都不为 0。**
+
+**接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按`implents`(如果这个类本身是接口的话则是`extends`) 后的接口顺序从左到右排列在接口索引集合中。**
+
+### 2.6 字段表集合
+
+```java
+ u2 fields_count;//Class 文件的字段的个数
+ field_info fields[fields_count];//一个类会可以有个字段
+```
+
+字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
+
+**field info(字段表) 的结构:**
+
+
+
+- **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
+- **name_index:** 对常量池的引用,表示的字段的名称;
+- **descriptor_index:** 对常量池的引用,表示字段和方法的描述符;
+- **attributes_count:** 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
+- **attributes[attributes_count]:** 存放具体属性具体内容。
+
+上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。
+
+**字段的 access_flags 的取值:**
+
+
+
+### 2.7 方法表集合
+
+```java
+ u2 methods_count;//Class 文件的方法的数量
+ method_info methods[methods_count];//一个类可以有个多个方法
+```
+
+methods_count 表示方法的数量,而 method_info 表示的方法表。
+
+Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
+
+**method_info(方法表的) 结构:**
+
+
+
+**方法表的 access_flag 取值:**
+
+
+
+注意:因为`volatile`修饰符和`transient`修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了`synchronized`、`native`、`abstract`等关键字修饰方法,所以也就多了这些关键字对应的标志。
+
+### 2.8 属性表集合
+
+```java
+ u2 attributes_count;//此类的属性表中的属性数
+ attribute_info attributes[attributes_count];//属性表集合
+```
+
+在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。
+
+## 参考
+
+-
+-
+-
+- 《实战 Java 虚拟机》
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md b/docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md
deleted file mode 100644
index d5b756c9..00000000
--- a/docs/java/可能是把Java内存区域讲的最清楚的一篇文章.md
+++ /dev/null
@@ -1,405 +0,0 @@
-
-
-
-- [写在前面(常见面试题)](#写在前面常见面试题)
- - [基本问题](#基本问题)
- - [拓展问题](#拓展问题)
-- [一 概述](#一-概述)
-- [二 运行时数据区域](#二-运行时数据区域)
- - [2.1 程序计数器](#21-程序计数器)
- - [2.2 Java 虚拟机栈](#22-java-虚拟机栈)
- - [2.3 本地方法栈](#23-本地方法栈)
- - [2.4 堆](#24-堆)
- - [2.5 方法区](#25-方法区)
- - [2.6 运行时常量池](#26-运行时常量池)
- - [2.7 直接内存](#27-直接内存)
-- [三 HotSpot 虚拟机对象探秘](#三-hotspot-虚拟机对象探秘)
- - [3.1 对象的创建](#31-对象的创建)
- - [3.2 对象的内存布局](#32-对象的内存布局)
- - [3.3 对象的访问定位](#33-对象的访问定位)
-- [四 重点补充内容](#四--重点补充内容)
- - [String 类和常量池](#string-类和常量池)
- - [String s1 = new String("abc");这句话创建了几个对象?](#string-s1--new-stringabc这句话创建了几个对象)
- - [8种基本类型的包装类和常量池](#8种基本类型的包装类和常量池)
-- [参考](#参考)
-
-
-## 写在前面(常见面试题)
-
-### 基本问题
-
-- **介绍下 Java 内存区域(运行时数据区)**
-- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)**
-- **对象的访问定位的两种方式(句柄和直接指针两种方式)**
-
-### 拓展问题
-
-- **String类和常量池**
-- **8种基本类型的包装类和常量池**
-
-
-## 一 概述
-
-对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。
-
-
-## 二 运行时数据区域
-Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。
-
-**JDK 1.8之前:**
-
-
-
-
-
-**JDK 1.8 :**
-
-
-
-
-
-**线程私有的:**
-
-- 程序计数器
-- 虚拟机栈
-- 本地方法栈
-
-**线程共享的:**
-
-- 堆
-- 方法区
-- 直接内存(非运行时数据区的一部分)
-
-
-### 2.1 程序计数器
-程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。**
-
-另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。**
-
-**从上面的介绍中我们知道程序计数器主要有两个作用:**
-
-1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
-2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
-
-**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。**
-
-### 2.2 Java 虚拟机栈
-
-**与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。**
-
-**Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
-
-**局部变量表主要存放了编译器可知的各种数据类型**(boolean、byte、char、short、int、float、long、double)、**对象引用**(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
-
-**Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。**
-
-- **StackOverFlowError:** 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
-- **OutOfMemoryError:** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
-
-Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
-
-**扩展:那么方法/函数如何调用?**
-
-Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。
-
-Java方法有两种返回方式:
-
-1. return 语句。
-2. 抛出异常。
-
-不管哪种返回方式都会导致栈帧被弹出。
-
-### 2.3 本地方法栈
-
-和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
-
-本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
-
-方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
-
-### 2.4 堆
-Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
-
-Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
-
-
-
-
-
-上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
-
-### 2.5 方法区
-
-方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。
-
-方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。
-
-#### 方法区和永久代的关系
-
-> 《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 **方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久带这一说法。
-
-#### 常用参数
-
-JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
-
-```java
--XX:PermSize=N //方法区(永久代)初始大小
--XX:MaxPermSize=N //方法区(永久代)最大大小,超过这个值将会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen
-```
-
-相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。**
-
-JDK 1.8 的时候,方法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取而代之是元空间,元空间使用的是直接内存。
-
-下面是一些常用参数:
-
-```java
--XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小)
--XX:MaxMetaspaceSize=N //设置Metaspace的最大大小
-```
-
-与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
-
-#### 为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢?
-
-整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到java.lang.OutOfMemoryError。你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
-
-当然这只是其中一个原因,还有很多底层的原因,这里就不提了。
-
-### 2.6 运行时常量池
-
-运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
-
-既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
-
-**JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。**
-
-
-——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189
-
-
-### 2.7 直接内存
-
-**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。**
-
-JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。
-
-本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
-
-
-## 三 HotSpot 虚拟机对象探秘
-通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。
-
-### 3.1 对象的创建
-下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
-
-
-**①类加载检查:** 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
-
-**②分配内存:** 在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。
-
-
-**内存分配的两种方式:(补充内容,需要掌握)**
-
-选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
-
-
-
-**内存分配并发问题(补充内容,需要掌握)**
-
-在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
-
-- **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。**
-- **TLAB:** 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配
-
-
-
-**③初始化零值:** 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
-
-**④设置对象头:** 初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
-
-
-**⑤执行 init 方法:** 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
-
-
-### 3.2 对象的内存布局
-
-在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:**对象头**、**实例数据**和**对齐填充**。
-
-**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
-
-**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。
-
-**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
-
-### 3.3 对象的访问定位
-建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种:
-
-1. **句柄:** 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
-
-
-2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
-
-
-
-**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。**
-
-
-
-
-## 四 重点补充内容
-
-### String 类和常量池
-
-**1 String 对象的两种创建方式:**
-
-```java
- String str1 = "abcd";
- String str2 = new String("abcd");
- System.out.println(str1==str2);//false
-```
-
-这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
-
-记住:只要使用new方法,便需要创建新的对象。
-
-
-
-**2 String 类型的常量池比较特殊。它的主要使用方法有两种:**
-
-- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
-- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
-
-```java
- String s1 = new String("计算机");
- String s2 = s1.intern();
- String s3 = "计算机";
- System.out.println(s2);//计算机
- System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
- System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象
-```
-**3 String 字符串拼接**
-```java
- String str1 = "str";
- String str2 = "ing";
-
- String str3 = "str" + "ing";//常量池中的对象
- String str4 = str1 + str2; //在堆上创建的新的对象
- String str5 = "string";//常量池中的对象
- System.out.println(str3 == str4);//false
- System.out.println(str3 == str5);//true
- System.out.println(str4 == str5);//false
-```
-
-
-尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
-### String s1 = new String("abc");这句话创建了几个对象?
-
-**创建了两个对象。**
-
-**验证:**
-
-```java
- String s1 = new String("abc");// 堆内存的地址值
- String s2 = "abc";
- System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
- System.out.println(s1.equals(s2));// 输出true
-```
-
-**结果:**
-
-```
-false
-true
-```
-
-**解释:**
-
-先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。
-
-### 8种基本类型的包装类和常量池
-
-- **Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。**
-- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。**
-
-```java
- Integer i1 = 33;
- Integer i2 = 33;
- System.out.println(i1 == i2);// 输出true
- Integer i11 = 333;
- Integer i22 = 333;
- System.out.println(i11 == i22);// 输出false
- Double i3 = 1.2;
- Double i4 = 1.2;
- System.out.println(i3 == i4);// 输出false
-```
-
-**Integer 缓存源代码:**
-
-```java
-/**
-*此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
-*/
- public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
-
-```
-
-**应用场景:**
-1. Integer i1=40;Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
-2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
-
-```java
- Integer i1 = 40;
- Integer i2 = new Integer(40);
- System.out.println(i1==i2);//输出false
-```
-**Integer比较更丰富的一个例子:**
-
-```java
- Integer i1 = 40;
- Integer i2 = 40;
- Integer i3 = 0;
- Integer i4 = new Integer(40);
- Integer i5 = new Integer(40);
- Integer i6 = new Integer(0);
-
- System.out.println("i1=i2 " + (i1 == i2));
- System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
- System.out.println("i1=i4 " + (i1 == i4));
- System.out.println("i4=i5 " + (i4 == i5));
- System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
- System.out.println("40=i5+i6 " + (40 == i5 + i6));
-```
-
-结果:
-
-```
-i1=i2 true
-i1=i2+i3 true
-i1=i4 false
-i4=i5 false
-i4=i5+i6 true
-40=i5+i6 true
-```
-
-解释:
-
-语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。
-
-## 参考
-
-- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》
-- 《实战java虚拟机》
--
--
--
--
-
-
-
-
-
diff --git a/docs/java/搞定JVM垃圾回收就是这么简单.md b/docs/java/搞定JVM垃圾回收就是这么简单.md
deleted file mode 100644
index 4530f3d3..00000000
--- a/docs/java/搞定JVM垃圾回收就是这么简单.md
+++ /dev/null
@@ -1,380 +0,0 @@
-
-上文回顾:[《可能是把Java内存区域讲的最清楚的一篇文章》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd)
-## 写在前面
-
-### 本节常见面试题:
-
-问题答案在文中都有提到
-
-- 如何判断对象是否死亡(两种方法)。
-- 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
-- 如何判断一个常量是废弃常量
-- 如何判断一个类是无用的类
-- 垃圾收集有哪些算法,各自的特点?
-- HotSpot为什么要分为新生代和老年代?
-- 常见的垃圾回收器有那些?
-- 介绍一下CMS,G1收集器。
-- Minor Gc和Full GC 有什么不同呢?
-
-### 本文导火索
-
-
-
-当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
-
-
-
-## 1 揭开JVM内存分配与回收的神秘面纱
-
-Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。
-
-Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
-
-**堆空间的基本结构:**
-
-
-
-
-
-上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
-
-
-
-
-
-### 1.1 对象优先在eden区分配
-
-目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
-
-大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。
-
-在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢?**
-
-- **新生代GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
-- **老年代GC(Major GC/Full GC)**:指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。
-
-**测试:**
-
-```java
-public class GCTest {
-
- public static void main(String[] args) {
- byte[] allocation1, allocation2;
- allocation1 = new byte[30900*1024];
- //allocation2 = new byte[900*1024];
- }
-}
-```
-通过以下方式运行:
-
-
-添加的参数:`-XX:+PrintGCDetails`
-
-
-运行结果(红色字体描述有误,应该是对应于JDK1.7的永久代):
-
-
-
-从上图我们可以看出eden区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用2000多k内存)。假如我们再为allocation2分配内存会出现什么情况呢?
-
-```java
-allocation2 = new byte[900*1024];
-```
-
-
-**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证:
-
-```java
-public class GCTest {
-
- public static void main(String[] args) {
- byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
- allocation1 = new byte[32000*1024];
- allocation2 = new byte[1000*1024];
- allocation3 = new byte[1000*1024];
- allocation4 = new byte[1000*1024];
- allocation5 = new byte[1000*1024];
- }
-}
-
-```
-
-
-### 1.2 大对象直接进入老年代
-大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
-
-**为什么要这样呢?**
-
-为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
-
-### 1.3 长期存活的对象将进入老年代
-既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。
-
-如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
-
-### 1.4 动态对象年龄判定
-
-为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。
-
-
-## 2 对象已经死亡?
-
-堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
-
-
-
-### 2.1 引用计数法
-
-给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。
-
-**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
-
-```java
-public class ReferenceCountingGc {
- Object instance = null;
- public static void main(String[] args) {
- ReferenceCountingGc objA = new ReferenceCountingGc();
- ReferenceCountingGc objB = new ReferenceCountingGc();
- objA.instance = objB;
- objB.instance = objA;
- objA = null;
- objB = null;
-
- }
-}
-```
-
-
-
-### 2.2 可达性分析算法
-
-这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
-
-
-
-
-### 2.3 再谈引用
-
-无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
-
-JDK1.2之前,Java中引用的定义很传统:如果reference类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
-
-JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
-
-
-
-**1.强引用**
-
-以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
-
-**2.软引用(SoftReference)**
-
-如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
-
-软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
-
-**3.弱引用(WeakReference)**
-
-如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
-
-弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
-
-**4.虚引用(PhantomReference)**
-
-"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
-
-**虚引用主要用来跟踪对象被垃圾回收的活动**。
-
-**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
-
-特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。
-
-### 2.4 不可达的对象并非“非死不可”
-
-即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
-
-被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
-
-### 2.5 如何判断一个常量是废弃常量
-
-运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?
-
-假如在常量池中存在字符串 "abc",如果当前没有任何String对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
-
-注意:我们在 [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd) 也讲了JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
-
-### 2.6 如何判断一个类是无用的类
-
-方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
-
-判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 **“无用的类”** :
-
-- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
-- 加载该类的 ClassLoader 已经被回收。
-- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
-
-虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
-
-
-## 3 垃圾收集算法
-
-
-
-### 3.1 标记-清除算法
-
-算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题:
-
-1. **效率问题**
-2. **空间问题(标记清除后会产生大量不连续的碎片)**
-
-
-
-### 3.2 复制算法
-
-为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
-
-
-
-### 3.3 标记-整理算法
-根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
-
-
-
-### 3.4 分代收集算法
-
-当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
-
-**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。**
-
-**延伸面试问题:** HotSpot为什么要分为新生代和老年代?
-
-根据上面的对分代收集算法的介绍回答。
-
-## 4 垃圾收集器
-
-
-
-**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
-
-虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。
-
-
-### 4.1 Serial收集器
-Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。
-
- **新生代采用复制算法,老年代采用标记-整理算法。**
-
-
-虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
-
-但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是个不错的选择。
-
-
-
-### 4.2 ParNew收集器
-**ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。**
-
- **新生代采用复制算法,老年代采用标记-整理算法。**
-
-
-它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
-
-**并行和并发概念补充:**
-
-- **并行(Parallel)** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
-
-- **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。
-
-
-### 4.3 Parallel Scavenge收集器
-
-Parallel Scavenge 收集器类似于ParNew 收集器。 **那么它有什么特别之处呢?**
-
-```
--XX:+UseParallelGC
-
- 使用Parallel收集器+ 老年代串行
-
--XX:+UseParallelOldGC
-
- 使用Parallel收集器+ 老年代并行
-
-```
-
-**Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。** Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
-
- **新生代采用复制算法,老年代采用标记-整理算法。**
-
-
-
-### 4.4.Serial Old收集器
-**Serial收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案。
-
-### 4.5 Parallel Old收集器
- **Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。
-
-### 4.6 CMS收集器
-
-**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。**
-
-**CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。**
-
-从名字中的**Mark Sweep**这两个词可以看出,CMS收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
-
-- **初始标记:** 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;
-- **并发标记:** 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
-- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
-- **并发清除:** 开启用户线程,同时GC线程开始对为标记的区域做清扫。
-
-
-
-从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点:
-
-- **对CPU资源敏感;**
-- **无法处理浮动垃圾;**
-- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。**
-
-### 4.7 G1收集器
-
-
-**G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.**
-
-被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备一下特点:
-
-- **并行与并发**:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
-- **分代收集**:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
-- **空间整合**:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
-- **可预测的停顿**:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。
-
-
-G1收集器的运作大致分为以下几个步骤:
-
-- **初始标记**
-- **并发标记**
-- **最终标记**
-- **筛选回收**
-
-
-**G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)**。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
-
-
-
-
-
-参考:
-
-- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》
-- https://my.oschina.net/hosee/blog/644618
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/java/这几道Java集合框架面试题几乎必问.md b/docs/java/这几道Java集合框架面试题几乎必问.md
deleted file mode 100644
index 18d276c4..00000000
--- a/docs/java/这几道Java集合框架面试题几乎必问.md
+++ /dev/null
@@ -1,288 +0,0 @@
-
-
-
-
-- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同)
- - [补充:数据结构基础之双向链表](#补充:数据结构基础之双向链表)
-- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别)
-- [HashMap的底层实现](#hashmap的底层实现)
- - [JDK1.8之前](#jdk18之前)
- - [JDK1.8之后](#jdk18之后)
-- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别)
-- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方)
-- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题)
-- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别)
-- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别)
-- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现)
- - [JDK1.7(上面有示意图)](#jdk17(上面有示意图))
- - [JDK1.8 (上面有示意图)](#jdk18-(上面有示意图))
-- [集合框架底层数据结构总结](#集合框架底层数据结构总结)
- - [Collection](#collection)
- - [1. List](#1-list)
- - [2. Set](#2-set)
- - [Map](#map)
- - [推荐阅读:](#推荐阅读:)
-
-
-
-## Arraylist 与 LinkedList 异同
-
-- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
-- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)
-- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。**
-- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。
-- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
-- **6.补充内容:RandomAccess接口**
-
-```java
-public interface RandomAccess {
-}
-```
-
-查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
-
-在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法
-
-```java
- public static
- int binarySearch(List extends Comparable super T>> list, T key) {
- if (list instanceof RandomAccess || list.size()>>:无符号右移,忽略符号位,空位都以0补齐
- return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- }
- ```
-对比一下 JDK1.7的 HashMap 的 hash 方法源码.
-
-```java
-static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
-
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
-}
-```
-
-相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
-
-所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
-
-
-
-
-
-### JDK1.8之后
-相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
-
-
-
->TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-**推荐阅读:**
-
-- 《Java 8系列之重新认识HashMap》 :[https://zhuanlan.zhihu.com/p/21673805](https://zhuanlan.zhihu.com/p/21673805)
-
-## HashMap 和 Hashtable 的区别
-
-1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
-2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
-3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
-4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
-5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
-
-**HasMap 中带有初始容量的构造函数:**
-
-```java
- public HashMap(int initialCapacity, float loadFactor) {
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal initial capacity: " +
- initialCapacity);
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: " +
- loadFactor);
- this.loadFactor = loadFactor;
- this.threshold = tableSizeFor(initialCapacity);
- }
- public HashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
-```
-
-下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。
-
-```java
- /**
- * Returns a power of two size for the given target capacity.
- */
- static final int tableSizeFor(int cap) {
- int n = cap - 1;
- n |= n >>> 1;
- n |= n >>> 2;
- n |= n >>> 4;
- n |= n >>> 8;
- n |= n >>> 16;
- return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
- }
-```
-
-## HashMap 的长度为什么是2的幂次方
-
-为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。
-
-**这个算法应该如何设计呢?**
-
-我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。**
-
-## HashMap 多线程操作导致死循环问题
-
-在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复制链表过程如下:
-以下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下:
-
- 线程一:读取到当前的 HashMap 情况,在准备扩容时,线程二介入
-
-
-
-线程二:读取 HashMap,进行扩容
-
-
-
-线程一:继续执行
-
-
-
-这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B
-
-**注意:jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647)
-
-
-## HashSet 和 HashMap 区别
-
-如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
-
-
-
-## ConcurrentHashMap 和 Hashtable 的区别
-
-ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
-
-- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
-- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
-
-**两者的对比图:**
-
-图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html
-
-HashTable:
-
-
-JDK1.7的ConcurrentHashMap:
-
-JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点
-Node: 链表节点):
-
-
-## ConcurrentHashMap线程安全的具体实现方式/底层具体实现
-
-### JDK1.7(上面有示意图)
-
-首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
-
-**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
-
-Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
-
-```java
-static class Segment extends ReentrantLock implements Serializable {
-}
-```
-
-一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
-
-### JDK1.8 (上面有示意图)
-
-ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
-
-synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
-
-
-
-## 集合框架底层数据结构总结
-### Collection
-
-#### 1. List
- - **Arraylist:** Object数组
- - **Vector:** Object数组
- - **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)
- 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html)
-
-#### 2. Set
- - **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素
- - **LinkedHashSet:** LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
- - **TreeSet(有序,唯一):** 红黑树(自平衡的排序二叉树。)
-
-### Map
- - **HashMap:** JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
- - **LinkedHashMap:** LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931)
- - **HashTable:** 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- - **TreeMap:** 红黑树(自平衡的排序二叉树)
-
-
-
-
-### 推荐阅读:
-
-- [jdk1.8中ConcurrentHashMap的实现原理](https://blog.csdn.net/fjse51/article/details/55260493)
-- [HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!](https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/)
-- [HASHMAP、HASHTABLE、CONCURRENTHASHMAP的原理与区别](http://www.yuanrengu.com/index.php/2017-01-17.html)
-- [ConcurrentHashMap实现原理及源码分析](https://www.cnblogs.com/chengxiao/p/6842045.html)
-- [java-并发-ConcurrentHashMap高并发机制-jdk1.8](https://blog.csdn.net/jianghuxiaojin/article/details/52006118#commentBox)
diff --git a/docs/javaguide面试突击版.md b/docs/javaguide面试突击版.md
new file mode 100644
index 00000000..af86da37
--- /dev/null
+++ b/docs/javaguide面试突击版.md
@@ -0,0 +1,55 @@
+今天(2020-03-07)终于把PDF版本的《JavaGuide面试突击版》搞定!废话不多说,直接上成品:
+
+
+
+### 如何获取
+
+公众号后台回复:“面试突击”即可。
+
+
+
+### 关于《JavaGuide面试突击版》
+
+JavaGuide 目前已经 70k+ Star ,目前已经是所有 Java 类别项目中 Star 数量第二的开源项目了。Star虽然很多,但是价值远远比不上 Dubbo 这些开源项目,希望以后可以多出现一些这样的国产开源项目。国产开源项目!加油!奥利给!
+
+随着越来越多的人参与完善这个项目,这个专注 “Java知识总结+面试指南 ” 项目的知识体系和内容的不断完善。JavaGuide 目前包括下面这两部分内容:
+
+1. **Java 核心知识总结**;
+2. **面试方向**:面试题、面试经验、备战面试系列文章以及面试真实体验系列文章
+
+内容的庞大让JavaGuide 显的有一点臃肿。所以,我决定将专门为 Java 面试所写的文章以及来自读者投稿的文章整理成 **《JavaGuide面试突击版》** 系列,同时也为了更加方便大家阅读查阅。起这个名字也犹豫了很久,大家如果有更好的名字的话也可以向我建议。暂时的定位是将其作为 PDF 电子书,并不会像 JavaGuide 提供在线阅读版本。我之前也免费分享过PDF 版本的《Java面试突击》,期间一共更新了 3 个版本,但是由于后面难以同步和订正所以就没有再更新。**《JavaGuide面试突击版》** pdf 版由于我工作流程的转变可以有效避免这个问题。
+
+另外,这段时间,向我提这个建议的读者也不是一个两个,我自己当然也有这个感觉。只是自己一直没有抽出时间去做罢了!毕竟这算是一个比较耗费时间的工程。加油!奥利给!
+
+这件事情具体耗费时间的地方是内容的排版优化(为了方便导出PDF生成目录),导出 PDF 我是通过 Typora 来做的。
+
+### 如何学习本项目
+
+提供了非常详细的目录,建议可以从头看是看一遍,如果基础不错的话也可以挑自己需要的章节查看。看的过程中自己要多思考,碰到不懂的地方,自己记得要勤搜索,需要记忆的地方也不要吝啬自己的脑子。
+
+### 关于更新
+
+**《JavaGuide面试突击版》** 预计一个月左右会有一次内容更新和完善,大家在我的公众号 **JavaGuide** 后台回复**“面试突击”** 即可获取最新版!另外,为了保证自己的辛勤劳动不被恶意盗版滥用,所以我添加了水印并且在一些内容注明版权,希望大家理解。
+
+### 如何贡献
+
+大家阅读过程中如果遇到错误的地方可以通过微信与我交流(ps:加过我微信的就不要重复添加了,这是另外一个账号,前一个已经满了)。
+
+
+
+希望大家给我提反馈的时候可以按照如下格式:
+
+> 我觉得2.3节Java基础的 2.3.1 这部分的描述有问题,应该这样描述:~巴拉巴拉~ 会更好!具体可以参考Oracle 官方文档,地址:~~~~。
+
+为了提高准确性已经不必要的时间花费,希望大家尽量确保自己想法的准确性。
+
+### 如何赞赏
+
+如果觉得本文档对你有帮助的话,欢迎加入我的知识星球。创建星球的目的主要是为了提高知识沉淀,微信群的弊端相比大家都了解。星球没有免费的原因是了设立门槛,提高进入读者的质量。我会在星球回答大家的问题,更新更多的大厂面试干货!
+
+
+
+我的知识星球的价格应该是我了解的圈子里面最低的,也就1顿饭钱吧!毕竟关注我的大部分还是学生,我打心底里希望自己分享的东西能对大家有帮助。
+
+
+
diff --git a/docs/network/干货:计算机网络知识总结.md b/docs/network/干货:计算机网络知识总结.md
index a5a50f10..a20e6f85 100644
--- a/docs/network/干货:计算机网络知识总结.md
+++ b/docs/network/干货:计算机网络知识总结.md
@@ -1,4 +1,3 @@
-> # 目录结构
### 1. [计算机概述 ](#一计算机概述)
### 2. [物理层 ](#二物理层)
### 3. [数据链路层 ](#三数据链路层 )
@@ -27,7 +26,7 @@
#### 广域网WAN(Wide Area Network)
任务是通过长距离运送主机发送的数据
#### 城域网MAN(Metropolitan Area Network)
- 用来讲多个局域网进行互连
+ 用来将多个局域网进行互连
#### 局域网LAN(Local Area Network)
学校或企业大多拥有多个互连的局域网
@@ -38,7 +37,7 @@
#### 分组(packet ):
因特网中传送的数据单元。由首部header和数据段组成。分组又称为包,首部可称为包头。
#### 存储转发(store and forward ):
- 路由器收到一个分组,先存储下来,再检查气首部,查找转发表,按照首部中的目的地址,找到合适的接口转发出去。
+ 路由器收到一个分组,先存储下来,再检查其首部,查找转发表,按照首部中的目的地址,找到合适的接口转发出去。
#### 带宽(bandwidth):
在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为b/s。
#### 吞吐量(throughput ):
@@ -52,20 +51,20 @@
大写字母I开头的Internet(互联网)是专用名词,它指全球最大的,开放的,由众多网络相互连接而成的特定的互联网,并采用TCP/IP协议作为通信规则,其前身为ARPANET。Internet的推荐译名为因特网,现在一般流行称为互联网。
- 3,路由器是实现分组交换的关键构件,其任务是转发受到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后在进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组有称为包。分组是在互联网中传送的数据单元,正式由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
+ 3,路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
-4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由西组成边缘部分,其作用是提供连通性和交换。
+4,互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成核心部分,其作用是提供连通性和交换。
5,计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S方式)和对等连接方式(P2P方式)。
-
+
6,客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。
-
-7,按照作用范围的不同,计算机网络分为广域网WAN,城域网MAN,局域网LAN,个人区域网PAN。
+
+7,按照作用范围的不同,计算机网络分为广域网WAN,城域网MAN,局域网LAN,个人区域网PAN。
8,计算机网络最常用的性能指标是:速率,带宽,吞吐量,时延(发送时延,处理时延,排队时延),时延带宽积,往返时间和信道利用率。
-
+
9,网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层以及其协议集合,称为网络的体系结构。
-
+
10,五层体系结构由应用层,运输层,网络层(网际层),数据链路层,物理层组成。运输层最主要的协议是TCP和UDP协议,网络层最重要的协议是IP协议。
## 二物理层
@@ -165,7 +164,7 @@
一种用于数据链路层实现中继,连接两个或多个局域网的网络互连设备。
#### 交换机(switch ):
广义的来说,交换机指的是一种通信系统中完成信息交换的设备。这里工作在数据链路层的交换机指的是交换式集线器,其实质是一个多接口的网桥
-
+
### (2),重要知识点总结
@@ -193,7 +192,7 @@
12,以太网的适配器具有过滤功能,它只接收单播帧,广播帧和多播帧。
-13,使用集线器可以在物理层扩展以太网(扩展后的以太网任然是一个网络)
+13,使用集线器可以在物理层扩展以太网(扩展后的以太网仍然是一个网络)
### (3),最重要的知识点
#### ① 数据链路层的点对点信道和广播信道的特点,以及这两种信道所使用的协议(PPP协议以及CSMA/CD协议)的特点
#### ② 数据链路层的三个基本问题:**封装成帧**,**透明传输**,**差错检测**
@@ -226,7 +225,7 @@
2,在互联网的交付有两种,一是在本网络直接交付不用经过路由器,另一种是和其他网络的间接交付,至少经过一个路由器,但最后一次一定是直接交付
-3,分类的IP地址由网络号字段(指明网络)和主机号字段(指明主机)组成。网络号字段最前面的类别指明IP地址的类别。IP地址市一中分等级的地址结构。IP地址管理机构分配IP地址时只分配网络号,主机号由得到该网络号的单位自行分配。路由器根据目的主机所连接的网络号来转发分组。一个路由器至少连接到两个网络,所以一个路由器至少应当有两个不同的IP地址
+3,分类的IP地址由网络号字段(指明网络)和主机号字段(指明主机)组成。网络号字段最前面的类别指明IP地址的类别。IP地址是一种分等级的地址结构。IP地址管理机构分配IP地址时只分配网络号,主机号由得到该网络号的单位自行分配。路由器根据目的主机所连接的网络号来转发分组。一个路由器至少连接到两个网络,所以一个路由器至少应当有两个不同的IP地址
4,IP数据报分为首部和数据两部分。首部的前一部分是固定长度,共20字节,是所有IP数据包必须具有的(源地址,目的地址,总长度等重要地段都固定在首部)。一些长度可变的可选字段固定在首部的后面。IP首部中的生存时间给出了IP数据报在互联网中所能经过的最大路由器数。可防止IP数据报在互联网中无限制的兜圈子。
@@ -236,7 +235,7 @@
7, 网际控制报文协议是IP层的协议.ICMP报文作为IP数据报的数据,加上首部后组成IP数据报发送出去。使用ICMP数据报并不是为了实现可靠传输。ICMP允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP报文的种类有两种 ICMP差错报告报文和ICMP询问报文。
-8,要解决IP地址耗尽的问题,最根本的办法是采用具有更大地址弓箭的新版本IP协议-IPv6。IPv6所带来的变化有①更大的地址空间(采用128位地址)②灵活的首部格式③改进的选项④支持即插即用⑤支持资源的预分配⑥IPv6的首部改为8字节对齐。另外IP数据报的目的地址可以是以下三种基本类型地址之一:单播,多播和任播
+8,要解决IP地址耗尽的问题,最根本的办法是采用具有更大地址空间的新版本IP协议-IPv6。IPv6所带来的变化有①更大的地址空间(采用128位地址)②灵活的首部格式③改进的选项④支持即插即用⑤支持资源的预分配⑥IPv6的首部改为8字节对齐。另外IP数据报的目的地址可以是以下三种基本类型地址之一:单播,多播和任播
9,虚拟专用网络VPN利用公用的互联网作为本机构专用网之间的通信载体。VPN内使用互联网的专用地址。一个VPN至少要有一个路由器具有合法的全球IP地址,这样才能和本系统的另一个VPN通过互联网进行通信。所有通过互联网传送的数据都需要加密
@@ -245,7 +244,7 @@
### (3),最重要知识点
#### ① 虚拟互联网络的概念
#### ② IP地址和物理地址的关系
-#### ③ 传统的分类的IP地址(包括子网掩码)和误分类域间路由选择CIDR
+#### ③ 传统的分类的IP地址(包括子网掩码)和无分类域间路由选择CIDR
#### ④ 路由选择协议的工作原理
## 五运输层
@@ -280,7 +279,7 @@
3,运输层的两个重要协议是用户数据报协议UDP和传输控制协议TCP。按照OSI的术语,两个对等运输实体在通信时传送的数据单位叫做运输协议数据单元TPDU(Transport Protocol Data Unit)。但在TCP/IP体系中,则根据所使用的协议是TCP或UDP,分别称之为TCP报文段或UDP用户数据报。
-4,UDP在传送数据之前不需要先建立连接,远地主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的,面向连接的运输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。
+4,UDP在传送数据之前不需要先建立连接,远地主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP确是一种最有效的工作方式。 TCP提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的,面向连接的传输服务,这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。
5,硬件端口是不同硬件设备进行交互的接口,而软件端口是应用层各种协议进程与运输实体进行层间交互的一种地址。UDP和TCP的首部格式中都有源端口和目的端口这两个重要字段。当运输层收到IP层交上来的运输层报文时,就能够 根据其首部中的目的端口号把数据交付应用层的目的应用层。(两个进程之间进行通信不光要知道对方IP地址而且要知道对方的端口号(为了找到对方计算机中的应用进程))
@@ -295,14 +294,14 @@
10,TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点。这样的端点就叫做套接字(socket)或插口。套接字用(IP地址:端口号)来表示。每一条TCP连接唯一被通信两端的两个端点所确定。
11,停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
-
+
12,为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停下来等待对方确认。这样可使信道上一直有数据不间断的在传送。这种传输方式可以明显提高信道利用率。
13,停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。连续ARQ协议可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
14,TCP报文段的前20个字节是固定的,后面有4n字节是根据需要增加的选项。因此,TCP首部的最小长度是20字节。
-15,TCP使用滑动窗口机制。发送窗口里面的序号表示允许发送的序号。发送窗口后沿的后面部分表示已发送且已收到确认,而发送窗口前沿的前面部分表示不晕与发送。发送窗口后沿的变化情况有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认)。发送窗口的前沿通常是不断向前移动的。一般来说,我们总是希望数据传输更快一些。但如果发送方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。
+15,TCP使用滑动窗口机制。发送窗口里面的序号表示允许发送的序号。发送窗口后沿的后面部分表示已发送且已收到确认,而发送窗口前沿的前面部分表示不允许发送。发送窗口后沿的变化情况有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认)。发送窗口的前沿通常是不断向前移动的。一般来说,我们总是希望数据传输更快一些。但如果发送方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。
16,在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
@@ -335,7 +334,7 @@
FTP 是File TransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。
基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。在FTP的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)。
"下载"文件就是从远程主机拷贝文件至自己的计算机上;"上传"文件就是将文件从自己的计算机中拷贝至远程主机上。用Internet语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。
-
+
#### 简单文件传输协议(TFTP):
TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69。
@@ -364,7 +363,7 @@
代理服务器(Proxy Server)是一种网络实体,它又称为万维网高速缓存。
代理服务器把最近的一些请求和响应暂存在本地磁盘中。当新请求到达时,若代理服务器发现这个请求与暂时存放的的请求相同,就返回暂存的响应,而不需要按URL的地址再次去互联网访问该资源。
代理服务器可在客户端或服务器工作,也可以在中间系统工作。
-
+
#### http请求头:
http请求头,HTTP客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是GET或者POST)。如有必要,客户程序还可以选择发送其他的请求头。
- Accept:浏览器可接受的MIME类型。
@@ -405,7 +404,7 @@
### (2),重要知识点总结
1,文件传输协议(FTP)使用TCP可靠的运输服务。FTP使用客户服务器方式。一个FTP服务器进程可以同时为多个用户提供服务。在进进行文件传输时,FTP的客户和服务器之间要先建立两个并行的TCP连接:控制连接和数据连接。实际用于传输文件的是数据连接。
-2,万维网客户程序与服务器之间进行交互使用的协议时超文本传输协议HTTP。HTTP使用TCP连接进行可靠传输。但HTTP本身是无连接、无状态的。HTTP/1.1协议使用了持续连接(分为非流水线方式和流水线方式)
+2,万维网客户程序与服务器之间进行交互使用的协议是超文本传输协议HTTP。HTTP使用TCP连接进行可靠传输。但HTTP本身是无连接、无状态的。HTTP/1.1协议使用了持续连接(分为非流水线方式和流水线方式)
3,电子邮件把邮件发送到收件人使用的邮件服务器,并放在其中的收件人邮箱中,收件人可随时上网到自己使用的邮件服务器读取,相当于电子邮箱。
diff --git a/docs/network/计算机网络.md b/docs/network/计算机网络.md
index 813ba89c..5a68352b 100644
--- a/docs/network/计算机网络.md
+++ b/docs/network/计算机网络.md
@@ -1,160 +1,106 @@
-
-
-- [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议)
- - [五层协议的体系结构](#五层协议的体系结构)
- - [1 应用层](#1-应用层)
- - [域名系统](#域名系统)
- - [HTTP协议](#http协议)
- - [2 运输层](#2-运输层)
- - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议)
- - [UDP 的主要特点](#udp-的主要特点)
- - [TCP 的主要特点](#tcp-的主要特点)
- - [3 网络层](#3-网络层)
- - [4 数据链路层](#4-数据链路层)
- - [5 物理层](#5-物理层)
- - [总结一下](#总结一下)
-- [二 TCP 三次握手和四次挥手\(面试常客\)](#二-tcp-三次握手和四次挥手面试常客)
- - [为什么要三次握手](#为什么要三次握手)
- - [为什么要传回 SYN](#为什么要传回-syn)
- - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- - [为什么要四次挥手](#为什么要四次挥手)
-- [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别)
-- [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输)
- - [停止等待协议](#停止等待协议)
- - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议)
- - [连续ARQ协议](#连续arq协议)
- - [滑动窗口](#滑动窗口)
- - [流量控制](#流量控制)
- - [拥塞控制](#拥塞控制)
-- [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五-在浏览器中输入url地址---显示主页的过程(面试常客))
-- [六 状态码](#六-状态码)
-- [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系)
-- [八 HTTP长连接、短连接](#八-http长连接、短连接)
-- [写在最后](#写在最后)
- - [计算机网络常见问题回顾](#计算机网络常见问题回顾)
- - [建议](#建议)
-
-
-
-相对与上一个版本的计算机网路面试知识总结,这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。
-## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议
-
-
-### 五层协议的体系结构
+## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议?
学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。
-
+
结合互联网的情况,自上而下地,非常简要的介绍一下各层的作用。
-### 1 应用层
+### 1.1 应用层
**应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统DNS**,支持万维网应用的 **HTTP协议**,支持电子邮件的 **SMTP协议**等等。我们把应用层交互的数据单元称为报文。
-#### 域名系统
+**域名系统**
> 域名系统(Domain Name System缩写 DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。
-#### HTTP协议
+**HTTP协议**
+
> 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科)
-### 2 运输层
+### 1.2 运输层
**运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
-#### 运输层主要使用以下两种协议
+**运输层主要使用以下两种协议:**
1. **传输控制协议 TCP**(Transmission Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。
2. **用户数据协议 UDP**(User Datagram Protocol)--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。
-#### UDP 的主要特点
-1. UDP 是无连接的;
-2. UDP 使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态(这里面有许多参数);
-3. UDP 是面向报文的;
-4. UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 直播,实时视频会议等);
-5. UDP 支持一对一、一对多、多对一和多对多的交互通信;
-6. UDP 的首部开销小,只有8个字节,比TCP的20个字节的首部要短。
-
-#### TCP 的主要特点
-1. TCP 是面向连接的。(就好像打电话一样,通话前需要先拨号建立连接,通话结束后要挂机释放连接);
-2. 每一条 TCP 连接只能有两个端点,每一条TCP连接只能是点对点的(一对一);
-3. TCP 提供可靠交付的服务。通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达;
-4. TCP 提供全双工通信。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接收缓存,用来临时存放双方通信的数据;
-5. 面向字节流。TCP 中的“流”(Stream)指的是流入进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。
+**TCP 与 UDP 的对比见问题三。**
-### 3 网络层
+### 1.3 网络层
**在 计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**。
这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。
-
这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称.
互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。
-### 4 数据链路层
-**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装程帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
+### 1.4 数据链路层
+**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装成帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。
控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。
-### 5 物理层
+### 1.5 物理层
在物理层上所传送的数据单位是比特。
**物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
在互联网使用的各种协中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议,而往往表示互联网所使用的整个TCP/IP协议族。
-### 总结一下
+### 1.6 总结一下
-上面我们对计算机网络的五层体系结构有了初步的了解,下面附送一张七层体系结构图总结一下。图片来源:https://blog.csdn.net/yaopeng_2005/article/details/7064869
-
+上面我们对计算机网络的五层体系结构有了初步的了解,下面附送一张七层体系结构图总结一下(图片来源于网络)。
+
+
## 二 TCP 三次握手和四次挥手(面试常客)
为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
-**漫画图解:**
+### 2.1 TCP 三次握手漫画图解
-图片来源:《图解HTTP》
-
+如下图所示,下面的两个机器人通过3次握手确定了对方能正确接收和发送消息(图片来源:《图解HTTP》)。
+
**简单示意图:**
-
+
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
-### 为什么要三次握手
+### 2.2 为什么要三次握手
**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
-第一次握手:Client 什么都不能确认;Server 确认了对方发送正常
+第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
-第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常
+第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常
+第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
-### 为什么要传回 SYN
+### 2.3 为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
-### 传了 SYN,为啥还要传 ACK
+### 2.4 传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
-
+
断开一个 TCP 连接则需要“四次挥手”:
@@ -163,8 +109,7 @@
- 服务器-关闭与客户端的连接,发送一个FIN给客户端
- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
-
-### 为什么要四次挥手
+### 2.5 为什么要四次挥手
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
@@ -172,12 +117,12 @@
上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
-## 三 TCP、UDP 协议的区别
-
+## 三 TCP,UDP 协议的区别
+
UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
-TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
+TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
## 四 TCP 协议如何保证可靠传输
@@ -187,68 +132,47 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接,
4. TCP 的接收端会丢弃重复的数据。
5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
-7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
+7. **ARQ协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
+### 4.1 ARQ协议
+**自动重传请求**(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
-### 停止等待协议
-- 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组;
+#### 停止等待ARQ协议
+- 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;
- 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认;
+**优点:** 简单
+
+**缺点:** 信道利用率低,等待时间长
**1) 无差错情况:**
-
-
发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。
**2) 出现差错情况(超时重传):**
-
-停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
+
+停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
**3) 确认丢失和确认迟到**
-- **确认丢失**:确认消息在传输过程丢失
- 
- 当A发送M1消息,B收到后,B向A发送了一个M1确认消息,但却在传输过程中丢失。而A并不知道,在超时计时过后,A重传M1消息,B再次收到该消息后采取以下两点措施:
+- **确认丢失** :确认消息在传输过程丢失。当A发送M1消息,B收到后,B向A发送了一个M1确认消息,但却在传输过程中丢失。而A并不知道,在超时计时过后,A重传M1消息,B再次收到该消息后采取以下两点措施:1. 丢弃这个重复的M1消息,不向上层交付。 2. 向A发送确认消息。(不会认为已经发送过了,就不再发送。A能重传,就证明B的确认消息丢失)。
+- **确认迟到** :确认消息在传输过程中迟到。A发送M1消息,B收到并发送确认。在超时时间内没有收到确认消息,A重传M1消息,B仍然收到并继续发送确认消息(B收到了2份M1)。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会,A收到了B第一次发送的对M1的确认消息(A也收到了2份确认消息)。处理如下:1. A收到重复的确认后,直接丢弃。2. B收到重复的M1后,也直接丢弃重复的M1。
- 1. 丢弃这个重复的M1消息,不向上层交付。
- 2. 向A发送确认消息。(不会认为已经发送过了,就不再发送。A能重传,就证明B的确认消息丢失)。
-- **确认迟到** :确认消息在传输过程中迟到
- 
- A发送M1消息,B收到并发送确认。在超时时间内没有收到确认消息,A重传M1消息,B仍然收到并继续发送确认消息(B收到了2份M1)。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会,A收到了B第一次发送的对M1的确认消息(A也收到了2份确认消息)。处理如下:
- 1. A收到重复的确认后,直接丢弃。
- 2. B收到重复的M1后,也直接丢弃重复的M1。
-
-### 自动重传请求 ARQ 协议
-停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。
-
-**优点:** 简单
-
-**缺点:** 信道利用率低
-
-### 连续ARQ协议
+#### 连续ARQ协议
连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
**优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
-
+
**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
-### 滑动窗口
+### 4.2 滑动窗口和流量控制
-- TCP 利用滑动窗口实现流量控制的机制。
-- 滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。
-- TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。
+**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
-### 流量控制
-
-- TCP 利用滑动窗口实现流量控制。
-- 流量控制是为了控制发送方发送速率,保证接收方来得及接收。
-- 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
-
-### 拥塞控制
+### 4.3 拥塞控制
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
@@ -257,21 +181,20 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接,
TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
- **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
- 
- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
- **快重传与快恢复:**
在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
- 
+## 五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)
-## 五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)
百度好像最喜欢问这个问题。
+
> 打开一个网页,整个过程会使用哪些协议
图解(图片来源:《图解HTTP》):
-
+
总体来说分为以下几个过程:
@@ -286,12 +209,9 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免**
- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
-
-
-
## 六 状态码
-
+
## 七 各种协议与HTTP协议之间的关系
@@ -299,9 +219,9 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免**
图片来源:《图解HTTP》
-
+
-## 八 HTTP长连接、短连接
+## 八 HTTP长连接,短连接
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
@@ -317,29 +237,60 @@ Connection:keep-alive
—— [《HTTP长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html)
+## 九 HTTP是不保存状态的协议,如何保存用户状态?
-## 写在最后
-### 计算机网络常见问题回顾
+HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个Session)。
-- ①TCP三次握手和四次挥手、
-- ②在浏览器中输入url地址->>显示主页的过程
-- ③HTTP和HTTPS的区别
-- ④TCP、UDP协议的区别
-- ⑤常见的状态码。
+在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。
+
+**Cookie 被禁用怎么办?**
+
+最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。
+
+
+
+## 十 Cookie的作用是什么?和Session有什么区别?
+
+Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
+
+ **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
+
+Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
+
+Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
+
+## 十一 HTTP 1.0和HTTP 1.1的主要区别是什么?
+
+> 这部分回答引用这篇文章 的一些内容。
+
+HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
+
+1. **长连接** : **在HTTP/1.0中,默认使用的是短连接**,也就是说每次请求都要重新建立一次连接。HTTP 是基于TCP/IP协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。**HTTP 1.1起,默认使用长连接** ,默认开启Connection: keep-alive。 **HTTP/1.1的持续连接有非流水线方式和流水线方式** 。流水线方式是客户在收到HTTP的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。
+1. **错误状态响应码** :在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
+1. **缓存处理** :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
+1. **带宽优化及网络连接的使用** :HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
+
+## 十二 URI和URL的区别是什么?
+
+- URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
+- URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
+
+URI的作用像身份证号一样,URL的作用更像家庭住址一样。URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。
+
+## 十三 HTTP 和 HTTPS 的区别?
+
+1. **端口** :HTTP的URL由“http://”起始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443。
+2. **安全性和资源消耗:** HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS 运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS高,但是 HTTPS 比HTTP耗费更多服务器资源。
+ - 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
+ - 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。
+
+## 建议
-### 建议
非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。
-
-
-### 参考
+## 参考
- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250)
- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466)
- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641)
-
-
-
-
-
-
+-
diff --git a/docs/operating-system/Shell.md b/docs/operating-system/Shell.md
index 9f3ae871..4a89061f 100644
--- a/docs/operating-system/Shell.md
+++ b/docs/operating-system/Shell.md
@@ -78,7 +78,7 @@ echo "helloworld!"
shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等...不过bash shell还是我们使用最多的。**
-(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
+(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)

@@ -260,7 +260,7 @@ echo $length #输出:5
echo $length2 #输出:5
# 输出数组第三个元素
echo ${array[2]} #输出:3
-unset array[1]# 删除下表为1的元素也就是删除第二个元素
+unset array[1]# 删除下标为1的元素也就是删除第二个元素
for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
unset arr_number; # 删除数组中的所有元素
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容
@@ -272,7 +272,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没
> 说明:图片来自《菜鸟教程》
Shell 编程支持下面几种运算符
-
+
- 算数运算符
- 关系运算符
- 布尔运算符
@@ -283,14 +283,14 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没

-我以加法运算符做一个简单的示例:
+我以加法运算符做一个简单的示例(注意:不是单引号,是反引号):
```shell
#!/bin/bash
a=3;b=3;
val=`expr $a + $b`
#输出:Total value : 6
-echo "Total value : $val
+echo "Total value : $val"
```
@@ -380,10 +380,10 @@ a 不等于 b
#!/bin/bash
a=3;
b=9;
-if [ $a = $b ]
+if [ $a -eq $b ]
then
echo "a 等于 b"
-elif [ $a > $b ]
+elif [ $a -gt $b ]
then
echo "a 大于 b"
else
@@ -394,7 +394,7 @@ fi
输出结果:
```
-a 大于 b
+a 小于 b
```
相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。
@@ -467,7 +467,7 @@ done
是的!变形金刚 是一个好电影
```
-**无线循环:**
+**无限循环:**
```shell
while true
@@ -482,16 +482,20 @@ done
```shell
#!/bin/bash
-function(){
+hello(){
echo "这是我的第一个 shell 函数!"
}
-function
+echo "-----函数开始执行-----"
+hello
+echo "-----函数执行完毕-----"
```
输出结果:
```
+-----函数开始执行-----
这是我的第一个 shell 函数!
+-----函数执行完毕-----
```
diff --git a/docs/operating-system/basis.md b/docs/operating-system/basis.md
new file mode 100644
index 00000000..6865b873
--- /dev/null
+++ b/docs/operating-system/basis.md
@@ -0,0 +1,336 @@
+大家好,我是 Guide 哥!很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如**进程管理**、**内存管理**、**虚拟内存**等等。
+
+文章形式通过大部分比较喜欢的面试官和求职者之间的对话形式展开。另外,Guide 哥也只是在大学的时候学习过操作系统,不过基本都忘了,为了写这篇文章这段时间看了很多相关的书籍和博客。如果文中有任何需要补充和完善的地方,你都可以在评论区指出。如果觉得内容不错的话,不要忘记点个在看哦!
+
+我个人觉得学好操作系统还是非常有用的,具体可以看我昨天在星球分享的一段话:
+
+
+
+这篇文章只是对一些操作系统比较重要概念的一个概览,深入学习的话,建议大家还是老老实实地去看书。另外, 这篇文章的很多内容参考了《现代操作系统》第三版这本书,非常感谢。
+
+
+
+## 一 操作系统基础
+
+面试官顶着蓬松的假发向我走来,只见他一手拿着厚重的 Thinkpad ,一手提着他那淡黄的长裙。
+
+
+
+### 1.1 什么是操作系统?
+
+👨💻**面试官** : 先来个简单问题吧!**什么是操作系统?**
+
+🙋 **我** :我通过以下四点向您介绍一下什么是操作系统吧!
+
+1. **操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石;**
+2. **操作系统本质上是运行在计算机上的软件程序 ;**
+3. **操作系统为用户提供一个与系统交互的操作界面 ;**
+4. **操作系统分内核与外壳(我们可以把外壳理解成围绕着内核的应用程序,而内核就是能操作硬件的程序)。**
+
+> 关于内核多插一嘴:内核负责管理系统的进程、内存、设备驱动程序、文件和网络系统等等,决定着系统的性能和稳定性。是连接应用程序和硬件的桥梁。
+> 内核就是操作系统背后黑盒的核心。
+
+
+
+### 1.2 系统调用
+
+👨💻**面试官** :**什么是系统调用呢?** 能不能详细介绍一下。
+
+🙋 **我** :介绍系统调用之前,我们先来了解一下用户态和系统态。
+
+
+
+根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
+
+1. 用户态(user mode) : 用户态运行的进程或可以直接读取用户程序的数据。
+2. 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
+
+说了用户态和系统态之后,那么什么是系统调用呢?
+
+我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
+
+也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
+
+这些系统调用按功能大致可分为如下几类:
+
+- 设备管理。完成设备的请求或释放,以及设备启动等功能。
+- 文件管理。完成文件的读、写、创建及删除等功能。
+- 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
+- 进程通信。完成进程之间的消息传递或信号传递等功能。
+- 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
+
+## 二 进程和线程
+
+### 2.1 进程和线程的区别
+
+👨💻**面试官**: 好的!我明白了!那你再说一下: **进程和线程的区别**。
+
+🙋 **我:** 好的! 下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!
+
+> 如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](<[https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F](https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域)>)
+
+
+
+从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
+
+**总结:** 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
+
+### 2.2 进程有哪几种状态?
+
+👨💻**面试官** : 那你再说说**进程有哪几种状态?**
+
+🙋 **我** :我们一般把进程大致分为 5 种状态,这一点和[线程](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md#6-%E8%AF%B4%E8%AF%B4%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E7%8A%B6%E6%80%81)很像!
+
+- **创建状态(new)** :进程正在被创建,尚未到就绪状态。
+- **就绪状态(ready)** :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
+- **运行状态(running)** :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
+- **阻塞状态(waiting)** :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
+- **结束状态(terminated)** :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
+
+
+
+### 2.3 进程间的通信方式
+
+👨💻**面试官** :**进程间的通信常见的的有哪几种方式呢?**
+
+🙋 **我** :大概有 7 种常见的进程间的通信方式。
+
+> 下面这部分总结参考了:[《进程间通信 IPC (InterProcess Communication)》](https://www.jianshu.com/p/c1015f5ffa74) 这篇文章,推荐阅读,总结的非常不错。
+
+1. **管道/匿名管道(Pipes)** :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
+1. **有名管道(Names Pipes)** : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循**先进先出(first in first out)**。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
+1. **信号(Signal)** :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
+1. **消息队列(Message Queuing)** :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。**消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。**
+1. **信号量(Semaphores)** :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
+1. **共享内存(Shared memory)** :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
+1. **套接字(Sockets)** : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
+
+### 2.4 线程间的同步的方式
+
+👨💻**面试官** :**那线程间的同步的方式有哪些呢?**
+
+🙋 **我** :线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:
+
+1. **互斥量(Mutex)**:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
+1. **信号量(Semphares)** :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
+1. **事件(Event)** :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操
+
+### 2.5 进程的调度算法
+
+👨💻**面试官** :**你知道操作系统中进程的调度算法有哪些吗?**
+
+🙋 **我** :嗯嗯!这个我们大学的时候学过,是一个很重要的知识点!
+
+为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
+
+- **先到先服务(FCFS)调度算法** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **短作业优先(SJF)的调度算法** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **时间片轮转调度算法** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
+- **多级反馈队列调度算法** :前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前**被公认的一种较好的进程调度算法**,UNIX 操作系统采取的便是这种调度算法。
+- **优先级调度** : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
+
+## 三 操作系统内存管理基础
+
+### 3.1 内存管理介绍
+
+👨💻 **面试官**: **操作系统的内存管理主要是做什么?**
+
+🙋 **我:** 操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。
+
+### 3.2 常见的几种内存管理机制
+
+👨💻 **面试官**: **操作系统的内存管理机制了解吗?内存管理有哪几种方式?**
+
+🙋 **我:** 这个在学习操作系统的时候有了解过。
+
+简单分为**连续分配管理方式**和**非连续分配管理方式**这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 **块式管理** 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如**页式管理** 和 **段式管理**。
+
+1. **块式管理** : 远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
+2. **页式管理** :把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
+3. **段式管理** : 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多 。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
+
+👨💻**面试官** : 回答的还不错!不过漏掉了一个很重要的 **段页式管理机制** 。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 **段页式管理机制** 中段与段之间以及段的内部的都是离散的。
+
+🙋 **我** :谢谢面试官!刚刚把这个给忘记了~
+
+
+
+### 3.3 快表和多级页表
+
+👨💻**面试官** : 页表管理机制中有两个很重要的概念:快表和多级页表,这两个东西分别解决了页表管理中很重要的两个问题。你给我简单介绍一下吧!
+
+🙋 **我** :在分页内存管理中,很重要的两点是:
+
+1. 虚拟地址到物理地址的转换要快。
+2. 解决虚拟地址空间大,页表也会很大的问题。
+
+#### 快表
+
+为了解决虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **快表** 来加速虚拟地址到物理地址的转换。我们可以把块表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
+
+使用快表之后的地址转换流程是这样的:
+
+1. 根据虚拟地址中的页号查快表;
+2. 如果该页在快表中,直接从快表中读取相应的物理地址;
+3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
+4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
+
+看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。
+
+#### 多级页表
+
+引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景,具体可以查看下面这篇文章
+
+- 多级页表如何节约内存:[https://www.polarxiong.com/archives/多级页表如何节约内存.html](https://www.polarxiong.com/archives/多级页表如何节约内存.html)
+
+#### 总结
+
+为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。 不论是快表还是多级页表实际上都利用到了程序的局部性原理,局部性原理在后面的虚拟内存这部分会介绍到。
+
+### 3.4 分页机制和分段机制的共同点和区别
+
+👨💻**面试官** : **分页机制和分段机制有哪些共同点和区别呢?**
+
+🙋 **我** :
+
+
+
+1. **共同点** :
+ - 分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
+ - 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
+2. **区别** :
+ - 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
+ - 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
+
+### 3.5 逻辑(虚拟)地址和物理地址
+
+👨💻**面试官** :你刚刚还提到了**逻辑地址和物理地址**这两个概念,我不太清楚,你能为我解释一下不?
+
+🙋 **我:** em...好的嘛!我们编程一般只有可能和逻辑地址打交道,比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
+
+### 3.6 CPU 寻址了解吗?为什么需要虚拟地址空间?
+
+👨💻**面试官** :**CPU 寻址了解吗?为什么需要虚拟地址空间?**
+
+🙋 **我** :这部分我真不清楚!
+
+
+
+于是面试完之后我默默去查阅了相关文档!留下了没有技术的泪水。。。
+
+> 这部分内容参考了 Microsoft 官网的介绍,地址:
+
+现代处理器使用的是一种称为 **虚拟寻址(Virtual Addressing)** 的寻址方式。**使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。** 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 **内存管理单元(Memory Management Unit, MMU)** 的硬件。如下图所示:
+
+
+
+**为什么要有虚拟地址空间呢?**
+
+先从没有虚拟地址空间的时候说起吧!没有虚拟地址空间的时候,**程序都是直接访问和操作的都是物理内存** 。但是这样有什么问题呢?
+
+1. 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
+2. 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
+
+**总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。**
+
+通过虚拟地址访问内存有以下优势:
+
+- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
+- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
+- 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
+
+## 四 虚拟内存
+
+### 4.1 什么是虚拟内存(Virtual Memory)?
+
+👨💻**面试官** :再问你一个常识性的问题!**什么是虚拟内存(Virtual Memory)?**
+
+🙋 **我** :这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用点开了很多占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。**为什么可以这样呢?** 正是因为 **虚拟内存** 的存在,通过 **虚拟内存** 可以让程序可以拥有超过系统物理内存大小的可用内存空间。另外,**虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)**。这样会更加有效地管理内存并减少出错。
+
+**虚拟内存**是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。**虚拟内存的重要意义是它定义了一个连续的虚拟地址空间**,并且 **把内存扩展到硬盘空间**。推荐阅读:[《虚拟内存的那点事儿》](https://juejin.im/post/59f8691b51882534af254317)
+
+维基百科中有几句话是这样介绍虚拟内存的。
+
+> **虚拟内存** 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如 RAM)的使用也更有效率。目前,大多数操作系统都使用了虚拟内存,如 Windows 家族的“虚拟内存”;Linux 的“交换空间”等。From:
+
+### 4.2 局部性原理
+
+👨💻**面试官** :要想更好地理解虚拟内存技术,必须要知道计算机中著名的**局部性原理**。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
+
+🙋 **我** :局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。
+
+> 以下内容摘自《计算机操作系统教程》 第 4 章存储器管理。
+
+早在 1968 年的时候,就有人指出我们的程序在执行的时候往往呈现局部性规律,也就是说在某个较短的时间段内,程序执行局限于某一小部分,程序访问的存储空间也局限于某个区域。
+
+局部性原理表现在以下两个方面:
+
+1. **时间局部性** :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
+2. **空间局部性** :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
+
+时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
+
+### 4.3 虚拟存储器
+
+👨💻**面试官** :都说了虚拟内存了。你再讲讲**虚拟存储器**把!
+
+🙋 **我** :
+
+> 这部分内容来自:[王道考研操作系统知识点整理](https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html)。
+
+基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器——**虚拟存储器**。
+
+实际上,我觉得虚拟内存同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。不得不感叹,程序世界几乎不是时间换空间就是空间换时间。
+
+### 4.4 虚拟内存的技术实现
+
+👨💻**面试官** :**虚拟内存技术的实现呢?**
+
+🙋 **我** :**虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。** 虚拟内存的实现有以下三种方式:
+
+1. **请求分页存储管理** :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
+2. **请求分段存储管理** :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
+3. **请求段页式存储管理**
+
+**这里多说一下?很多人容易搞混请求分页与分页存储管理,两者有何不同呢?**
+
+请求分页存储管理建立在分页管理之上。他们的根本区别是是否将程序全部所需的全部地址空间都装入主存,这也是请求分页存储管理可以提供虚拟内存的原因,我们在上面已经分析过了。
+
+它们之间的根本区别在于是否将一作业的全部地址空间同时装入主存。请求分页存储管理不要求将作业全部地址空间同时装入主存。基于这一点,请求分页存储管理可以提供虚存,而分页存储管理却不能提供虚存。
+
+不管是上面那种实现方式,我们一般都需要:
+
+1. 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
+2. **缺页中断**:如果**需执行的指令或访问的数据尚未在内存**(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段**调入到内存**,然后继续执行程序;
+3. **虚拟地址空间** :逻辑地址到物理地址的变换。
+
+### 4.5 页面置换算法
+
+👨💻**面试官** :虚拟内存管理很重要的一个概念就是页面置换算法。那你说一下 **页面置换算法的作用?常见的页面置换算法有哪些?**
+
+🙋 **我** :
+
+> 这个题目经常作为笔试题出现,网上已经给出了很不错的回答,我这里只是总结整理了一下。
+
+地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
+
+> **缺页中断** 就是要访问的**页**不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
+
+当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。
+
+- **OPT 页面置换算法(最佳页面置换算法)** :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
+- **FIFO(First In First Out) 页面置换算法(先进先出页面置换算法)** : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
+- **LRU (Least Currently Used)页面置换算法(最近最久未使用页面置换算法)** :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
+- **LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法)** : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
+
+## Reference
+
+- 《计算机操作系统—汤小丹》第四版
+- [《深入理解计算机系统》](https://book.douban.com/subject/1230413/)
+- [https://zh.wikipedia.org/wiki/输入输出内存管理单元](https://zh.wikipedia.org/wiki/输入输出内存管理单元)
+- [https://baike.baidu.com/item/快表/19781679](https://baike.baidu.com/item/快表/19781679)
+- https://www.jianshu.com/p/1d47ed0b46d5
+-
+-
+-
+- 王道考研操作系统知识点整理: https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html
\ No newline at end of file
diff --git a/docs/operating-system/后端程序员必备的Linux基础知识.md b/docs/operating-system/linux.md
similarity index 89%
rename from docs/operating-system/后端程序员必备的Linux基础知识.md
rename to docs/operating-system/linux.md
index 65cc9eae..d7288985 100644
--- a/docs/operating-system/后端程序员必备的Linux基础知识.md
+++ b/docs/operating-system/linux.md
@@ -1,3 +1,5 @@
+点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
+
- [一 从认识操作系统开始](#一-从认识操作系统开始)
@@ -22,12 +24,15 @@
+推荐一个Github开源的Linux学习指南(Java工程师向):
+
> 学习Linux之前,我们先来简单的认识一下操作系统。
## 一 从认识操作系统开始
+
### 1.1 操作系统简介
-我通过以下四点介绍什么操作系统:
+我通过以下四点介绍什么是操作系统:
- **操作系统(Operation System,简称OS)是管理计算机硬件与软件资源的程序,是计算机系统的内核与基石;**
- **操作系统本质上是运行在计算机上的软件程序 ;**
@@ -92,7 +97,7 @@ Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是
**常见目录说明:**
-- **/bin:** 存放二进制可执行文件(ls,cat,mkdir等),常用命令一般都在这里;
+- **/bin:** 存放二进制可执行文件(ls、cat、mkdir等),常用命令一般都在这里;
- **/etc:** 存放系统管理和配置文件;
- **/home:** 存放所有用户文件的根目录,是用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示;
- **/usr :** 用于存放系统应用程序;
@@ -142,10 +147,10 @@ Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/)
注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。
6. **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r代表递归拷贝
-
+
注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归
7. **`rm [-rf] 目录`:** 删除目录(删)
-
+
注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包
@@ -160,12 +165,12 @@ Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/)
注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化
3. **`vim 文件`:** 修改文件的内容(改)
-
+
vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。
**在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤:**
- vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)
+ vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)
4. **`rm -rf 文件`:** 删除文件(删)
同目录删除:熟记 `rm -rf` 文件 即可
@@ -181,14 +186,14 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g
其中:
z:调用gzip压缩命令进行压缩
-
+
c:打包文件
-
+
v:显示运行过程
-
+
f:指定文件名
-比如:加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令:**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或:`tar -zcvf test.tar.gz /test/`**
+比如:假如test目录下有三个文件分别是:aaa.txt bbb.txt ccc.txt,如果我们要打包test目录并指定压缩后的压缩包名称为test.tar.gz可以使用命令:**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt`或:`tar -zcvf test.tar.gz /test/`**
**2)解压压缩包:**
@@ -201,7 +206,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g
1 将/test下的test.tar.gz解压到当前目录下可以使用命令:**`tar -xvf test.tar.gz`**
-2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf xxx.tar.gz -C /usr`**(- C代表指定解压的位置)
+2 将/test下的test.tar.gz解压到根目录/usr下:**`tar -xvf test.tar.gz -C /usr`**(- C代表指定解压的位置)
### 4.5 Linux的权限命令
@@ -235,21 +240,21 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g
**文件和目录权限的区别:**
对文件和目录而言,读写执行表示不同的意义。
-
+
对于文件:
-| 权限名称 | 可执行操作 |
+| 权限名称 | 可执行操作 |
| :-------- | --------:|
-| r | 可以使用cat查看文件的内容 |
-|w | 可以修改文件的内容 |
+| r | 可以使用cat查看文件的内容 |
+|w | 可以修改文件的内容 |
| x | 可以将其运行为二进制文件 |
对于目录:
-| 权限名称 | 可执行操作 |
+| 权限名称 | 可执行操作 |
| :-------- | --------:|
-| r | 可以查看目录下列表 |
-|w | 可以创建和删除目录下文件 |
+| r | 可以查看目录下列表 |
+|w | 可以创建和删除目录下文件 |
| x | 可以使用cd进入目录 |
@@ -260,7 +265,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g
一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用ls ‐ahl命令可以看到文件的所有者 也可以使用chown 用户名 文件名来修改文件的所有者 。
- **文件所在组**
-
+
当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组 用ls ‐ahl命令可以看到文件的所有组 也可以使用chgrp 组名 文件名来修改文件所在的组。
- **其它组**
@@ -275,6 +280,8 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g
**`chmod u=rwx,g=rw,o=r aaa.txt`**
+**`chmod -R u=rwx,g=rwx,o=rwx ./log`** // 递归给log目录下的所有文件授权
+

上述示例还可以使用数字表示:
@@ -326,25 +333,40 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过
### 4.8 其他常用命令
- **`pwd`:** 显示当前所在位置
+
+- `sudo + 其他命令`:以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。
+
- **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color代表高亮显示
+
- **`ps -ef`/`ps -aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程),也可使用 `pgrep redis -a`。
注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。
+
- **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。)
先用ps查找进程,然后用kill杀掉
+
- **网络通信命令:**
- 查看当前系统的网卡信息:ifconfig
- 查看与某台机器的连接情况:ping
- 查看当前系统的端口使用:netstat -an
+
- **net-tools 和 iproute2 :**
`net-tools`起源于BSD的TCP/IP工具箱,后来成为老版本Linux内核中配置网络功能的工具。但自2001年起,Linux社区已经对其停止维护。同时,一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools,只支持`iproute2`。linux ip命令类似于ifconfig,但功能更强大,旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux)
-- **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机,同时送出警告信息给登入用户。
+
+- **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机,同时送出警告信息给登入用户。
+
- **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。
-
-
-
-
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取!
+
+**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/questions/java-big-data.md b/docs/questions/java-big-data.md
new file mode 100644
index 00000000..2ede8233
--- /dev/null
+++ b/docs/questions/java-big-data.md
@@ -0,0 +1,71 @@
+写这篇文章主要是为了回答球友的一个提问,提问如下:
+
+
+
+刚好自己对这方面有一丁点的见解,所以回答一下这位老哥的问题,如果能够解决他的问题,我也会很高兴。下面仅仅代表个人一件,环境大家批评指正与完善!
+
+先说一下自己的经历,大学的时候我从大二开始学习 Java ,然后学了大半年多的安卓。之后就开始学习 Java 后台,学习完 Java 后台一些常用的知识比如 Java基础、Spring、MyBatis等等之后。因为感觉大数据领域发展也挺不错的,所以就接触了一些大数据方面的知识比如当时大数据领域的霸主 Hadoop 。
+
+> 我当时学习了很多比较古老的技术比如现在基本不会用的 JSP、Struts2等等。另外,我
+
+所以,我当时在找工作之间也纠结过自己到底是投大数据岗位还是Java后台开发岗位。
+
+主要纠结点如下:
+
+1. **薪资:** 大数据当时的薪资水平高于 Java 后台开发很多;
+2. **前景:** 我个人感觉大数据岗位的发展前景很好;
+3. **个人偏见:** 感觉大数据开发比 Java后台开发听着高大上点(哈哈,当时的我就是这么真实);
+
+不过在我分析了大部分公司的大数据岗位的要求以及自身的优势(Java后台开发的实际经验)之后还是义无反顾的只投递 Java 后台开发岗位。
+
+先来看一下几家典型的互联网公司对大数据工程师的要求(我找的都是允许应届毕业生投递的岗位):
+
+**SHEIN**
+
+> 很多人可以不了解这家低调的公司,主要原因是因为 SHEIN目前的主要业务是出口跨境电商,用户基本集中在海外。SHEIN 这些年的发展非常不错,总的来说是一家值得去的公司。
+
+
+
+SHEIN 的大数据岗位的要求写的还是比较有代代表性的!但是我觉得加上:**有扎实的Java基础、熟悉多线程与JVM相关原理** 这一条可能会更好!
+
+一家公司可能并不具有代表性,我们再来找一家公司的大数据岗位看看。
+
+**Alibaba**
+
+
+
+说明一下,阿里巴巴大的大数据开发岗位的描述其实挺友好的比如这样描述:
+
+> “如果你有参与过数据处理、分析、挖掘等相关项目更好”、“如果你对Hadoop、Hive、Hbase等分布式平台有一定的理解更好”。
+
+实际是这样吗?nonono!我信你个鬼,你个糟老头子坏的很!毕竟这么多人竞争这一个岗位,不会像描述的这么简单。
+
+如果你对 HDFS、HBase、Hadoop 甚至是 Elasticsearch这些不了解的话,还是会很难入场。
+
+**总结一下(偏大厂)大数据岗位的对于应届生的基本要求(社招的其实也差不多,对于经验要求会更高):**
+
+1. **算法和数据结构是最基本的(比如手写快排、手撕红黑树)。**
+2. **有扎实的Java基础、熟悉多线程与JVM相关原理。**
+3. **熟练使用 Linux ,熟悉一门脚本语言 shell 或者 Python**
+4. 熟悉Hadoop架构和工作原理、MapReduce编程、HDFS;熟悉Hive,最好有HQL优化经验;
+5. **熟练掌握 Spark 及 Spark Streaming开发,有实际项目研发经验更佳;**
+6. 熟悉 Elasticsearch、Kafka等技术会是加分项;
+7. ......
+
+所以,总的来说不论是对于 **Java 后台开发还是大数据开发都会要求你的数据结构和算法 Java 基础、多线程、jvm 底层这些掌握的要很好。** 很多人 Java 后台的人转大数据开发很快的原因也是在这里。
+
+正常一点的大数据面试还是比较有难度的,比如如果你写了你会 Spark 的话,他就会问题你:
+
+1. 什么场景下用的Spark ?解决了什么问题?
+2. Spark 执行机制了解吗?
+3. Spark 内存模型了解吗?
+4. ......
+
+另外,如果你的简历上写了你会 Spring 这些东西的话,面试官应该也会一并提问。可以看出现在的大数据岗位没有强制性要求你有 web 开发经验,在我那一年的时候,大部分大数据开发岗位都要求你还要有 web 开发经验。
+
+
+
+
+
+
+
diff --git a/docs/questions/java-learning-path-and-methods.md b/docs/questions/java-learning-path-and-methods.md
new file mode 100644
index 00000000..b41c1f98
--- /dev/null
+++ b/docs/questions/java-learning-path-and-methods.md
@@ -0,0 +1,242 @@
+到目前为止,我觉得不管是在公众号后台、知乎还是微信上面我被问的做多的就是:“大佬,有没有 Java 学习路线和方法”。所以,这部分单独就自己的学习经历来说点自己的看法。
+
+**下面的学习路线以及方法是笔主根据个人学习经历总结改进后得出,我相信照着这条学习路线来你的学习效率会非常高。**
+
+学习某个知识点的过程中如果不知道看什么书的话,可以查看这篇文章 :[Java 学习必备书籍推荐终极版!](https://github.com/Snailclimb/JavaGuide/blob/master/docs/data/java-recommended-books.md)。
+
+另外,很重要的一点:**建议使用 Intellij IDEA 进行编码,可以单独抽点时间学习 Intellij IDEA 的使用。**
+
+**下面提到的一些视频,[公众号](#公众号 "公众号")后台回复关键“1”即可获取!**
+
+先说一个初学者很容易犯的错误:上来就通过项目学习。
+
+**很多初学者上来就像通过做项目学习,特别是在公司,我觉得这个是不太可取的。** 如果的 Java基础或者 Spring Boot 基础不好的话,建议自己先提前学习一下之后再开始看视频或者通过其他方式做项目。 **还有点事,我不知道为什么大家都会说边跟着项目边学习做的话效果最好,我觉得这个要加一个前提是你对这门技术有基本的了解或者说你对编程有了一定的了解。**
+
+**关于如何学习且听我从一个电商系统网站的创建来说起。假如我们要创建一个基于Java的分布式/微服务电商系统的话,我们可以按照下面的学习路线来做:**
+
+> 首选第一步我们肯定是要从 Java 基础来学习的(如果你没有计算机基础知识的话推荐看一下《计算机导论》这类入门书籍)。
+
+### step 1:Java 基础
+
+**《Java 核心技术卷 1/2》** 和 **《Head First Java》** 这两本书在我看来都是入门 Java 的很不错的书籍 (**《Java 核心技术卷 1/2》** 知识点更全,我更推荐这本书),我倒是觉得 **《Java 编程思想》** 有点属于新手劝退书的意思,慎看,建议有点基础后再看。你也可以边看视频边看书学习(黑马、尚硅谷、慕课网的视频都还行)。对于 Java8 新特性的东西,我建议你基础学好之后可以看一下,暂时看不太明白也没关系,后面抽时间再回过头来看。
+
+看完之后,你可以用自己学的东西实现一个简单的 Java 程序,也可以尝试用 Java 解决一些编程问题,以此来将自己学到的东西付诸于实践。
+
+**不太建议学习 Java基础的时候通过做游戏来巩固。为什么培训班喜欢通过这种方式呢?说白点就是为了找到你的G点(不好意思开车了哈)。新手学习完Java基础后做游戏一般是不太现实的,还不如找一些简单的程序问题解决一下比如简单的算法题。**
+
+**记得多总结!打好基础!把自己重要的东西都记录下来。** API 文档放在自己可以看到的地方,以备自己可以随时查阅。为了能让自己写出更优秀的代码,**《Effective Java》**、**《重构》** 这两本书没事也可以看看。
+
+另外,学习完之后可以看一下下面这几篇文章,检查一下自己的学习情况。这几篇文章不是我吹,可能是全网最具价值的 Java 基础知识总结,毕竟是在我的 JavaGuide开源的,经过了各路大佬以及我的不断完善。
+
+这几篇文章总结的知识点在 Java 后端面试中的出场率也非常高哦!
+
+1. [**Java 基础知识**](https://snailclimb.gitee.io/javaguide/#/docs/java/Java基础知识)
+2. [**Java 基础知识疑难点/易错点**](https://snailclimb.gitee.io/javaguide/#/docs/java/Java疑难点)
+3. [【加餐】一些重要的Java程序设计题](https://snailclimb.gitee.io/javaguide/#/docs/java/Java程序设计题)
+4. [【选看】J2EE 基础知识](https://snailclimb.gitee.io/javaguide/#/docs/java/J2EE基础知识)
+
+> 我们的网站需要运行在“操作系统”之上(一般是部署在Linux系统),并且我们与网站的每次交互都需要经过“网络”,需要经历三次握手和四次挥手才能简历连接,需要HTTP才能发出请求已经拿到网站后台的相应。所以第二步,我推荐可以适当花时间看一下 **操作系统与计算机网络 方面的知识。** 但是,不做强求!你抽时间一定要补上就行!
+
+### step 2(可选):操作系统与计算机网络
+
+操作系统这方面我觉得掌握操作系统的基础知识和 Linux 的常用命令就行以及一些重要概念就行了。
+
+关于操作系统的话,我没有什么操作系统方面的书籍可以推荐,因为我自己也没认真看过几本。因为操作系统比较枯燥的原因,我建议这部分看先看视频学可能会比较好一点。我推荐一个 Github 上开源的哈工大《操作系统》课程给大家吧!地址:https://github.com/hoverwinter/HIT-OSLab 。
+
+另外,对于 Linux 我们要掌握基本的使用就需要对一些常用命令非常熟悉比如:目录切换命令、目录操作命令、文件的操作命令、压缩或者解压文件的命令等等。推荐一个 Github 上学习 Linux 的开源文档:[《Java 程序员眼中的 Linux》](https://github.com/judasn/Linux-Tutorial "《Java 程序员眼中的 Linux》")
+
+计算机网络方面的学习,我觉得掌握基本的知识就行了,不需要太深究,一般面试对这方面要求也不高,毕竟不是专门做网络的。推荐 **《网络是怎样连接的》** 、**《图解 HTTP》** 这两本书来看,这两本书都属于比较有趣易懂的类型,也适合没有基础的人来看。
+
+> 我们写程序的都知道一个公式叫做 **“程序设计 = 算法 + 数据结构”**。我们想让我们的网站的地盘更加牢固的话,我觉得数据结构与算法还是很有必要学习的。所以第三步,我推荐可以适当花时间看一下 **数据结构与算法** 但是,同样不做强求!你抽时间一定要补上就行!
+
+### step 3(可选):数据结构与算法
+
+如果你想进入大厂的话,我推荐你在学习完 Java基础之后,就开始每天抽出一点时间来学习算法和数据结构。为了提高自己的编程能力,你也可以坚持刷 **[Leetcode](https://leetcode-cn.com "Leetcode")**。就目前国内外的大厂面试来说,刷 Leetcode 可以说已经成了不得不走的一条路。
+
+对于想要入门算法和数据结构的朋友,建议看这两本书 **《算法图解》** 和 **《大话数据结构》**,这两本书虽然算不上很经典的书籍,但是比较有趣,对于刚入门算法和数据结构的朋友非常友好。**《算法导论》** 非常经典,但是对于刚入门的就不那么友好了。
+
+另外,还有一本非常赞的算法书推荐给各位,这本书的名字就叫 **《算法》**,书中的代码都是用 Java 语言编写。这本书的优点太多太多比如它的讲解基础而全面、对阅读者比较友好等等。我觉得这本书唯一的缺点就是太厚了 (小声 BB,可能和作者讲解某些知识点的时候有点啰嗦有关)。除了这本书之外,**《剑指 offer》** 、**《编程珠玑》** 、**《编程之美》** 这三本书都被很多大佬推荐过了,对于算法面试非常有帮助。**《算法之美》** 这本书也非常不错,非常适合闲暇的时候看。
+
+> 我们网站的页面搭建需要前端的知识,我们前端也后端的交互也需要前端的知识。所以第四步,我推荐你可以了解一下前端知识,不过不需要学的太精通。自己对与前端知识有了基本的了解之后通过
+
+### step 4:前端知识
+
+这一步主要是学习前端基础 (HTML、CSS、JavaScript),当然 BootStrap、Layui 等等比较简单的前端框架你也可以了解一下。网上有很多这方面资源,我只推荐一个大部分初学这些知识都会看的网站:http://www.w3school.com.cn/ ,这个网站用来回顾知识也很不错 。推荐先把 HTML、CSS、JS 的基础知识过一遍,然后通过一个实际的前端项目来巩固。
+
+另外,没记错的话 Spring Boot官方推荐的是模板引擎是 thymeleaf ,这东西和HTML很像,了解了基本语法之后很容易上手。 结合layui,booystrap这些框架的话也能做成比较美观的页面。开发一些简单的页面比如一个后端项目就是为了做个简单的前端页面做某些操作的话直接用thymeleaf就好。
+
+现在都是前后端分离,就目前来看大部分项目都优先选择 React、Angular、Vue 这些厉害的框架来开发,这些框架的上手要求要高一些。如果你想往全栈方向发展的话(笔主目前的方向,我用 React 在公司做过两个小型项目),建议先把 JS 基础打好,然后再选择 React、Angular、Vue 其中的一个来认真学习一下。国内使用 Vue 比较多一点,国外一般用的是 React 和 Angular。
+
+ 如何和后端交互呢?一般使用在 React、Vue这些框架的时候使用Axios比较多。
+
+> 我们网站的数据比如用户信息、订单信息都需要存储,所以,下一步我推荐你学习 MySQl这个被广泛运用于各大网站的数据库。不光要学会如何写 sql 语句,更好的是还要搞清诸如索引这类重要的概念。
+
+### step 5:MySQL
+
+学习 MySQL 的基本使用,基本的增删改查,SQL 命令,索引、存储过程这些都学一下吧!推荐书籍 **《SQL 基础教程(第 2 版)》**(入门级)、**《高性能 MySQL : 第 3 版》**(进阶)、**《MySQL 必知必会》**。
+
+下面这些 MySQL 相关的文章强烈推荐你看看:
+
+1. [**【推荐】MySQL/数据库 知识点总结**](https://snailclimb.gitee.io/javaguide/#/docs/database/MySQL)
+2. [**阿里巴巴开发手册数据库部分的一些最佳实践**](https://snailclimb.gitee.io/javaguide/#/docs/database/阿里巴巴开发手册数据库部分的一些最佳实践)
+3. [**一千行MySQL学习笔记**](https://snailclimb.gitee.io/javaguide/#/docs/database/一千行MySQL命令)
+4. [MySQL高性能优化规范建议](https://snailclimb.gitee.io/javaguide/#/docs/database/MySQL高性能优化规范建议)
+5. [数据库索引总结](https://snailclimb.gitee.io/javaguide/#/docs/database/MySQL Index)
+6. [事务隔离级别(图文详解)](https://snailclimb.gitee.io/javaguide/#/docs/database/事务隔离级别(图文详解))
+7. [一条SQL语句在MySQL中如何执行的](https://snailclimb.gitee.io/javaguide/#/docs/database/一条sql语句在mysql中如何执行的)
+
+> 正式开发之前我们还要一些准备工具比如熟悉你的ide,熟悉Maven来帮助我们引入相关jar依赖,使用 Docker来帮助我们安装常用的软件。
+
+### step 6:常用工具
+
+非常重要!非常重要!特别是 Git和 Docker。
+
+1. **IDEA**:熟悉基本操作以及常用快捷。
+2. **Maven** :建议学习常用框架之前可以提前花半天时间学习一下**Maven**的使用。(到处找 Jar 包,下载 Jar 包是真的麻烦费事,使用 Maven 可以为你省很多事情)。
+3. **Git** :基本的 Git 技能也是必备的,试着在学习的过程中将自己的代码托管在 Github 上。([Git 入门](https://github.com/Snailclimb/JavaGuide/blob/master/docs/tools/Git.md))
+4. **Docker** :学着用 Docker 安装学习中需要用到的软件比如 MySQL ,这样方便很多,可以为你节省不少时间。([Docker 入门](https://github.com/Snailclimb/JavaGuide/blob/master/docs/tools/Docker.md))
+
+> 利用常用框架可以极大程度简化我们的开发工作。学习完了常用工具之后,我们就可以开始常用框架的学习啦!
+
+### step 7:常用框架
+
+学习 Struts2(可不用学)、**Spring**、**SpringMVC**、**Hibernate**、**Mybatis**、**shiro** 等框架的使用, (可选) 熟悉 **Spring 原理**(大厂面试必备),然后很有必要学习一下 **SpringBoot** ,**学好 SpringBoot 真的很重要**。很多公司对于应届生都是直接上手 **SpringBoot**,不过如果时间允许的话,我还是推荐你把 **Spring**、**SpringMVC** 提前学一下。
+
+关于 SpringBoot ,推荐看一下笔主开源的 [Spring Boot 教程](https://github.com/Snailclimb/springboot-guide "Spring Boot 教程") (SpringBoot 核心知识点总结。 基于 Spring Boot 2.19+)。
+
+**Spring 真的很重要!** 一定要搞懂 AOP 和 IOC 这两个概念。Spring 中 bean 的作用域与生命周期、SpringMVC 工作原理详解等等知识点都是非常重要的,一定要搞懂。
+
+推荐看文档+视频结合的方式,中途配合实战来学习,学习期间可以多看看 JavaGuide 对于[常用框架的总结](https://github.com/Snailclimb/JavaGuide#%E5%B8%B8%E7%94%A8%E6%A1%86%E6%9E%B6 "常用框架的总结")。
+
+**另外,都 2019 年了,咱千万不要再学 JSP 了好不?**
+
+推荐阅读:
+
+#### [Spring](https://snailclimb.gitee.io/javaguide/#/?id=spring)
+
+1. [Spring 学习与面试](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring)
+2. **[Spring 常见问题总结](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions)**
+3. [Spring中 Bean 的作用域与生命周期](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringBean)
+4. [SpringMVC 工作原理详解](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringMVC-Principle)
+5. [Spring中都用到了那些设计模式?](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring-Design-Patterns)
+
+#### [SpringBoot](https://snailclimb.gitee.io/javaguide/#/?id=springboot)
+
+- **[SpringBoot 指南/常见面试题总结](https://github.com/Snailclimb/springboot-guide)**
+
+#### [MyBatis](https://snailclimb.gitee.io/javaguide/#/?id=mybatis)
+
+- [MyBatis常见面试题总结](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/mybatis/mybatis-interview)
+
+### step 8:多线程的简单使用
+
+多线程这部分内容可能会比较难以理解和上手,前期可以先简单地了解一下基础,到了后面有精力和能力后再回来仔细看。推荐 **《Java 并发编程之美》** 或者 **《实战 Java 高并发程序设计》** 这两本书。我目前也在重构一份我之前写的多线程学习指南,后面会更新在公众号里面。
+
+学习完多线程之后可以通过下面这些问题检测自己是否掌握。
+
+**Java 多线程知识基础:**
+
+1. 什么是线程和进程?
+2. 请简要描述线程与进程的关系,区别及优缺点?
+3. 说说并发与并行的区别?
+4. 为什么要使用多线程呢?
+5. 使用多线程可能带来什么问题?
+6. 说说线程的生命周期和状态?
+7. 什么是上下文切换?
+8. 什么是线程死锁?如何避免死锁?
+9. 说说 sleep() 方法和 wait() 方法区别和共同点?
+10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+
+**Java 多线程知识进阶:**
+
+1. synchronized 关键字:① 说一说自己对于 synchronized 关键字的了解;② 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗;③ 讲一下 synchronized 关键字的底层原理;④ 说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗;⑤ 谈谈 synchronized 和 ReentrantLock 的区别。
+2. volatile 关键字: ① 讲一下 Java 内存模型;② 说说 synchronized 关键字和 volatile 关键字的区别。
+3. ThreadLocal:① 简介;② 原理;③ 内存泄露问题。
+4. 线程池:① 为什么要用线程池?;② 实现 Runnable 接口和 Callable 接口的区别;③ 执行 execute() 方法和 submit() 方法的区别是什么呢?;④ 如何创建线程池。
+5. Atomic 原子类: ① 介绍一下 Atomic 原子类;② JUC 包中的原子类是哪 4 类?;③ 讲讲 AtomicInteger 的使用;④ 能不能给我简单介绍一下 AtomicInteger 类的原理。
+6. AQS :① 简介;② 原理;③ AQS 常用组件。
+
+### **step 9:分布式**
+
+1. 学习 **Dubbo、Zookeeper来实现简单的分布式服务**
+2. **学习 Redis** 来提高访问速度,减少对 MySQL数据库的依赖;
+3. **学习 Elasticsearch** 的使用,来为我们的网站增加搜索功能
+4. 学习常见的**消息队列**(比如**RabbitMQ、Kafka**)来解耦我们的服务(ActiveMq不要学了,已经淘汰);
+5. ......
+
+到了这一步你应该是有基础的一个 Java程序员了,我推荐你可以通过一个分布式项目来学习。觉得应该是掌握这些知识点比较好的一种方式了,另外,**推荐边看视频边自己做,遇到不懂的知识点要及时查阅网上博客和相关书籍,这样学习效果更好。**
+
+**一定要学会拓展知识,养成自主学习的意识。** 黑马项目对这些知识点的介绍都比较蜻蜓点水。
+
+> 继续深入学习的话,我们要了解Netty、JVM这些东西。
+
+### step 10:深入学习
+
+可以再回来看一下多线程方面的知识,还可以利用业余时间学习一下 **[NIO](https://github.com/Snailclimb/JavaGuide#io "NIO")** 和 **Netty** ,这样简历上也可以多点东西。如果想去大厂,**[JVM](https://github.com/Snailclimb/JavaGuide#jvm "JVM")** 的一些知识也是必学的(**Java 内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM 内存管理**)推荐《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(最新第二版》和《实战 Java 虚拟机》,如果嫌看书麻烦的话,你也可以看我整理的文档。
+
+另外,现在微服务特别火,很多公司在面试也明确要求需要微服务方面的知识。如果有精力的话可以去学一下 SpringCloud 生态系统微服务方面的东西。
+
+> **微服务的概念庞大,技术种类也很多,但是目前大型互联网公司广泛采用的,**实话实话这些东西我不在行,自己没有真实做过微服务的项目。不过下面是我自己总结的一些关于微服务比价重要的知识,选学。
+
+### step 11:微服务
+
+这部分太多了,选择性学习。
+
+相关技术:
+
+1. **网关** :kong,soul;
+2. **分布式调用链**: SkyWalking、Zipkin
+3. **日志系统:** Kibana
+4. ......
+
+Spring Cloud 相关:
+
+1. Eureka:服务注册与发现;
+2. Ribbon:负载均衡;
+3. Hytrix :熔断;
+4. Zuul :网关;
+5. Spring Cloud Config:配置中心;
+
+Spring Cloud Alibaba也是很值得学习的:
+
+1. **[Sentinel](https://github.com/alibaba/Sentinel "Sentinel")** :A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)。
+2. **[dubbo](https://github.com/apache/dubbo "dubbo")** :Apache Dubbo 是一个基于 Java 的高性能开源 RPC 框架。
+3. **[nacos](https://github.com/alibaba/nacos "nacos")** :Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 可以作为 Dubbo 的注册中心来使用。
+4. **[seata](https://github.com/seata/seata "seata")** : Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
+5. **[RocketMQ](https://github.com/apache/rocketmq "RocketMQ")** :阿里巴巴开源的一款高性能、高吞吐量的分布式消息中间件。
+
+### 总结
+
+我上面主要概括一下每一步要学习的内容,对学习规划有一个建议。知道要学什么之后,如何去学呢?我觉得学习每个知识点可以考虑这样去入手:
+
+1. **官网(大概率是英文,不推荐初学者看)**。
+2. **书籍(知识更加系统完全,推荐)**。
+3. **视频(比较容易理解,推荐,特别是初学的时候。慕课网和哔哩哔哩上面有挺多学习视频可以看,只直接在上面搜索关键词就可以了)**。
+4. **网上博客(解决某一知识点的问题的时候可以看看)**。
+
+这里给各位一个建议,**看视频的过程中最好跟着一起练,要做笔记!!!**
+
+**最好可以边看视频边找一本书籍看,看视频没弄懂的知识点一定要尽快解决,如何解决?**
+
+首先百度/Google,通过搜索引擎解决不了的话就找身边的朋友或者认识的一些人。另外,一定要进行项目实战!很多人这时候就会问没有实际项目让我做怎么办?我觉得可以通过下面这几种方式:
+
+1. 在网上找一个符合自己能力与找工作需求的实战项目视频或者博客跟着老师一起做。做的过程中,你要有自己的思考,不要浅尝辄止,对于很多知识点,别人的讲解可能只是满足项目就够了,你自己想多点知识的话,对于重要的知识点就要自己学会去往深处学。
+2. Github 或者码云上面有很多实战类别项目,你可以选择一个来研究,为了让自己对这个项目更加理解,在理解原有代码的基础上,你可以对原有项目进行改进或者增加功能。
+3. 自己动手去做一个自己想完成的东西,遇到不会的东西就临时去学,现学现卖(这种方式比较难,初学不推荐用这种方式,因为你脑海中没有基本的概念,写出来的代码一般会很难或者根本就做不出来一个像样的东西)。
+4. ......
+
+**做项目不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。**
+
+**一定要学会分配自己时间,要学的东西很多,真的很多,搞清楚哪些东西是重点,哪些东西仅仅了解就够了。一定不要把精力都花在了学各种框架上,算法和数据结构真的很重要!**
+
+另外,**学习的过程中有一个可以参考的文档很重要,非常有助于自己的学习**。我当初弄 JavaGuide: https://github.com/Snailclimb/JavaGuide 的很大一部分目的就是因为这个。**客观来说,相比于博客,JavaGuide 里面的内容因为更多人的参与变得更加准确和完善。**
+
+### 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号 "公众号")后台回复 **"Java 面试突击"** 即可免费领取!
+
+**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
+
+
diff --git a/docs/questions/java-learning-website-blog.md b/docs/questions/java-learning-website-blog.md
new file mode 100644
index 00000000..06ef65fe
--- /dev/null
+++ b/docs/questions/java-learning-website-blog.md
@@ -0,0 +1,80 @@
+## 推荐两个视频学习网站
+
+### 慕课网
+
+第一个推荐的学习网站应该是慕课网(慕课网私聊我打钱哈!),在我初学的时候,这个网站对我的帮助挺大的,里面有很多免费的课程,也有很多付费的课程。如果你没有特殊的需求,一般免费课程就够自己学的了。
+
+
+
+### 哔哩哔哩
+
+想不到弹幕追番/原创视频小站也被推荐了吧!不得不说哔哩哔哩上面的学习资源还是很多的,现在有很多年轻人都在上面学习呢!哈哈哈 大部分年轻人最爱的小破站可是受到过央视表扬的。被誉为年轻人学习的首要阵地,哔哩哔哩干杯!
+
+不过在哔哩哔哩上面越靠前的视频就是最好的视频或者说最适合你的视频,也是要筛选一下的。
+
+
+
+### 极客时间
+
+主打付费学习的一个付费学习社区(极客时间私聊我打钱哈!)。不过课程的质量大部分都挺高的,我自己也看了里面很多的课程,并且很多课程都是 Java 领域大佬级别的人物将的。
+
+
+
+## 推荐一些文字类型学习网站/博客
+
+### Github
+
+最牛逼的程序员交流网站!!!没有之一。一定要多逛逛!上面有很多好东西,比如我搜索 Java(它竟然给我返回贼多 javascript 的项目,啥意思???)
+
+
+
+比如我搜索女装,emm....然后就出来了这些东西,捂住眼睛,不敢看!
+
+
+
+### 菜鸟教程
+
+对于新手入门来说很不错的网站,大部分教程都是针对的入门级别。优点是网站教程内容比较完善并且内容质量也是有保障的。
+
+
+
+### w3cschool
+
+和菜鸟教程类似的一个网站,里面的教程也很齐全。
+
+
+
+### Stackoverflow
+
+**Stack Overflow**是一个程序设计领域的问答网站,网站允许注册用户提出或回答问题。和知乎很像,重大的一点不同是 Stack Overflow 可以对问题进行打分。
+
+
+
+### leetcode
+
+网站地址:https://leetcode-cn.com/
+
+工作之余没事去刷个算法题,岂不是美滋滋。
+
+
+
+### 一些不错的技术交流社区推荐
+
+1. **掘金**:[https://juejin.im/](https://juejin.im/ "https://juejin.im/") 。
+2. **segmentfault** : [https://segmentfault.com/](https://segmentfault.com/ "https://segmentfault.com/")
+3. **博客园** : [https://www.cnblogs.com/](https://www.cnblogs.com/ "https://www.cnblogs.com/")
+4. **慕课网手记** :[https://www.imooc.com/article](https://www.imooc.com/article "https://www.imooc.com/article")
+5. **知乎** :[https://www.zhihu.com/](https://www.zhihu.com/ "https://www.zhihu.com/")
+
+### 一些不错的博客/Github 推荐
+
+- SnailClimb 的 Github :[https://github.com/Snailclimb](https://github.com/Snailclimb "https://github.com/Snailclimb") 。(自荐一波哈!主要专注在 Java 基础和进阶、Spring、Spiring Boot、Java 面试这方面。)
+- 徐靖峰个人博客 :[https://www.cnkirito.moe/](https://www.cnkirito.moe/ "https://www.cnkirito.moe/")(探讨 Java 生态的知识点,内容覆盖分布式服务治理、微服务、性能调优、各类源码分析)
+- 田小波:[http://www.tianxiaobo.com/](http://www.tianxiaobo.com/ "http://www.tianxiaobo.com/") (Java 、Spring 、MyBatis 、Dubbo)
+- 周立的博客: [http://www.itmuch.com/](http://www.itmuch.com/ "http://www.itmuch.com/")(Spring Cloud、Docker、Kubernetes,及其相关生态的技术)
+- Hollis: [https://www.hollischuang.com/](https://www.hollischuang.com/ "https://www.hollischuang.com/") (Java 后端)
+- 方志朋的专栏 : [https://www.fangzhipeng.com/](https://www.fangzhipeng.com/ "https://www.fangzhipeng.com/") (Java 面试 Java 并发 openresty kubernetes Docker 故事 )
+- 纯洁的微笑 : [http://www.ityouknow.com/](http://www.ityouknow.com/ "http://www.ityouknow.com/") (Java、SpringBoot、Spring Cloud)
+- 芋道源码: [http://www.iocoder.cn/](http://www.iocoder.cn/ "http://www.iocoder.cn/") (专注源码)。
+- 欢迎自荐
+- ......
\ No newline at end of file
diff --git a/docs/questions/java-training-4-month.md b/docs/questions/java-training-4-month.md
new file mode 100644
index 00000000..82c7690a
--- /dev/null
+++ b/docs/questions/java-training-4-month.md
@@ -0,0 +1,104 @@
+问题描述:
+
+> 最近在北京华软科技公司看到一个招聘,去咨询了人事部,他说培训四个月就能上岗,并且不要学费,上岗后再每还1000元,还一年,这个可靠吗?本人高中毕业,四个月能学会吗?谢谢了!!!
+
+下面是正文:
+
+一般说不要学费,上岗后每月再还1000元这种十有八九都不靠谱,就算你把合同看的再仔细,别人也总有各种办法去刁难你。
+
+另外,目前的互联网行业已经完全不是它刚开始盛行的样子了。在互联网爆火🔥的初期,你可能会简单用一下语言就能找到一个不错的工作。那时候,即使是没有学历支撑直接从培训班出来的基本也都找到了还算是不错的工作。但是,现在已经完全不一样了。我觉得主要可以从以下几个方面讲:
+
+1. **没有学历支撑,直接从培训班出来的找工作会很难,甚至找不到**;
+2. **面试的难度可以说一年比一年难,学的人越来越多,和你竞争的也越来越多,特别是像面试阿里、腾讯、字节跳动这样的大厂,你可能要和更多人去竞争。“面试造火箭,入职拎螺丝”想想也是正常,毕竟这么多人去竞争那少数的 offer,如果不难点的话,区分度就没那么明显了**;
+3. 学习计算机专业的越来越多,和你竞争的也越来越多,需求就那么一些,人多了之后,平均工资水平以后应该不会和其他行业差别这么大。但是,我个人感觉技术厉害的还是会很吃香。只是,普通的程序员的工资可能比不上前几年了。
+
+**养成一个学习习惯和编程习惯真的太重要了,一个好习惯的养成真的对后面的学习有很大帮助。** 说实话我自己当初在这方面吃了不少亏,很多比较好的习惯我也是后面自己才慢慢发现,所以这里想着重给大家说一下有哪些好的学习和编程习惯。另外,**不要在意自己会多少框架,真的没有一点用!**
+
+下面是一些我觉得还不错的编程好习惯,希望对大家有帮助。
+
+## 编程好习惯推荐
+
+> **下面这些我都总结在了 Github 上,更多内容可以通过这个链接查看:** https://github.com/Snailclimb/programmer-advancement 。
+
+### 正确提问
+
+我们平时任何时候都离不开提问特别是初学的时候,但是真正知道如何正确的提问的人很少。问别人问题前不要来一句“在吗”,你说你问了在吗我是回复好还是不回复好呢 ?不要让别人给你发 32 位的JDK,除非你是喜欢那个人。
+
+更多关于如何提问的内容,详见 github 上开源版『提问的智慧』 ,抽时间看一下,我想看完之后应该会有很多收获。
+
+更多内容可以查看我的这篇原创文章:[如何提问](docs/how-to-ask.md)
+
+### 健康生活
+
+我一直觉得这一方面是最重要的,我想很多人和我一样会无意识间忽略它,等到真的身体不舒服了,你才开始意识到健康生活的重要性。
+
+1. 除非万不得已,不要熬夜了。熬夜的危害就不用多说了,秃头加内分泌失调,你懂得!
+2. 看电脑45分钟之后,起来走5分钟,看看远方放松一下。不要觉得这5分钟浪费时间,相反,这5分钟可能为你带来更大的效率提升。
+3. 可以考虑买一个电脑架子,保护好自己脊椎的同时,办公体验也会提升很多。
+4. 可以下载一个护眼宝,感觉可以护眼模式挺棒的,非常适合我们这种需要经常盯着电脑的人使用,强烈安利。
+
+### 高效搜索
+
+尽量用 google 查找技术资料以及自己在学习中遇到的一些问题。
+
+### 解决 bug
+
+程序遇到问题先在 stackoverflow 找找,大部分别人已经遇到过了。如果上面没有的话,再考虑其他解决办法。实在解决不了的话,再去问你觉得有能力帮你解决的人(注意描述好自己的问题,不要随便截一个Bug 图)。
+
+### 善于总结
+
+学习完任何一门知识后,你可能当时看视频感觉老师讲的挺容易懂的。但是,过几天后你发现你忘的一干二净,别人问你一个类似的问题,你一点思路都没有。所以,我推荐你学完一门知识后不光要及时复习,还要做好总结,让知识形成一个体系。另外,你可以假想自己要给别人讲这个知识点,你能不能把这个知识点讲清楚呢?如果不能,说明你对这个知识点还没有彻底了解。这也就是人们经常说的费曼学习技巧。
+
+总结的方式:
+
+1. 有道云笔记、OneNote......这类专门用来记录笔记的软件上;
+2. 思维导图;
+3. 通过写博客输出。可以考虑自己搭建一个博客(hexo+GithubPages非常简单),你也可以在简书、掘金......等等技术交流社区写博客。Markdown 格式参考: 中文文案排版指北:
+
+### 写博客
+
+写博客有哪些好处:
+
+1. 对知识有更加深的认识,让自己的知识体系更加完整;
+2. 督促自己学习;
+3. 可能会带来不错的经济收入;
+4. 提升个人影响力;
+5. 拥有更多机会;
+6. ......
+
+**总的来说,写博客是一件利己利彼的事情。你可能会从中收获到很多东西,你写的东西也可能对别人也有很大的帮助。但是,写博客还是比较耗费自己时间的,你需要和工作做好权衡。**
+
+**分享是一种美德,任何行业都不是靠单打独斗的,写博客、写好博客是一个程序员很好的习惯。我为人人,人人为我!**
+
+更多内容可以查看我的这篇原创文章:[我为什么推荐你写博客?](./docs/我为什么推荐你写博客.md)
+
+### 多用 Github
+
+没事多去Github转转,如果有能力可以参与到一些开源项目中。多看看别人开源的优秀项目,看看别人的代码和设计思路,看的多了,你的编程思想也会慢慢得到提升。除了这些优秀的开源项目之外,Github上面还有很多不错的开源文档、开源资料什么的,我觉得对我们平时学习都挺有帮助。Github用得好还能装一下,毕竟人家还是一个全英文网站,咳咳咳。
+
+### 实践
+
+多去实践,将学到的东西运用到实际项目中去。很多人都找我抱怨过没有实际项目让自己去做,怎么能有项目经验呢?如果实在没有实际项目让你去做,我觉得你可以通过下面几种方式:
+
+1. 在网上找一个符合自己能力与找工作需求的实战项目视频或者博客跟着老师一起做。做的过程中,你要有自己的思考,不要浅尝辄止,对于很多知识点,别人的讲解可能只是满足项目就够了,你自己想多点知识的话,对于重要的知识点就要自己学会去往深出学。
+2. Github或者码云上面有很多实战类别项目,你可以选择一个来研究,为了让自己对这个项目更加理解,在理解原有代码的基础上,你可以对原有项目进行改进或者增加功能。
+3. 自己动手去做一个自己想完成的东西,遇到不会的东西就临时去学,现学现卖。
+
+### 注意代码规范
+
+从学习编程的第一天起就要养成不错的编码习惯,包、类、方法的命名这些是最基本的。
+
+推荐阅读:
+
+- 阿里巴巴Java开发手册(详尽版)[https://github.com/alibaba/p3c/blob/master/阿里巴巴Java开发手册(详尽版).pdf](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf)
+- Google Java编程风格指南:
+- Effective Java第三版中文版:
+
+### 沟通能力
+
+程序员也离不开沟通。你可能需要与客户交流需求,还要和同事交流项目问题,还有可能定期需要向领导汇报项目进展情况。所以,我觉得不错的沟通能力也是一个优秀的程序员应该有的基本素质。
+
+## 学习方法和学习路线推荐
+
+推荐查看我的这篇文章[《可能是最适合你的Java学习方法和路线推荐》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/questions/java-learning-path-and-methods.md),文中提到的学习路线以及方法是笔主根据个人学习经历总结改进后得出,我相信照着这条学习路线来你的学习效率会非常高。
+
diff --git a/docs/system-design/authority-certification/JWT-advantages-and-disadvantages.md b/docs/system-design/authority-certification/JWT-advantages-and-disadvantages.md
new file mode 100644
index 00000000..4d16c413
--- /dev/null
+++ b/docs/system-design/authority-certification/JWT-advantages-and-disadvantages.md
@@ -0,0 +1,93 @@
+# JWT 身份认证优缺点分析以及常见问题解决方案
+
+之前分享了一个使用 Spring Security 实现 JWT 身份认证的 Demo,文章地址:[适合初学者入门 Spring Security With JWT 的 Demo](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485622&idx=1&sn=e9750ed63c47457ba1896db8dfceac6a&chksm=cea2477df9d5ce6b7af20e582c6c60b7408a6459b05b849394c45f04664d1651510bdee029f7&token=684071313&lang=zh_CN&scene=21#wechat_redirect)。 Demo 非常简单,没有介绍到 JWT 存在的一些问题。所以,单独抽了一篇文章出来介绍。为了完成这篇文章,我查阅了很多资料和文献,我觉得应该对大家有帮助。
+
+相关阅读:
+
+- [《一问带你区分清楚Authentication,Authorization以及Cookie、Session、Token》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485626&idx=1&sn=3247aa9000693dd692de8a04ccffeec1&chksm=cea24771f9d5ce675ea0203633a95b68bfe412dc6a9d05f22d221161147b76161d1b470d54b3&token=684071313&lang=zh_CN&scene=21#wechat_redirect)
+- [适合初学者入门 Spring Security With JWT 的 Demo](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485622&idx=1&sn=e9750ed63c47457ba1896db8dfceac6a&chksm=cea2477df9d5ce6b7af20e582c6c60b7408a6459b05b849394c45f04664d1651510bdee029f7&token=684071313&lang=zh_CN&scene=21#wechat_redirect)
+- [Spring Boot 使用 JWT 进行身份和权限验证](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485640&idx=1&sn=0ff147808318d53b371f16bb730c96ef&chksm=cea24703f9d5ce156ba67662f6f3f482330e8e6ebd9d44c61bf623083e9b941d8a180db6b0ea&token=1533246333&lang=zh_CN#rd)
+
+## Token 认证的优势
+
+ 相比于 Session 认证的方式来说,使用 token 进行身份认证主要有下面三个优势:
+
+### 1.无状态
+
+token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。但是,也正是由于 token 的无状态,也导致了它最大的缺点:当后端在token 有效期内废弃一个 token 或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户 Logout 的话,token 也还有效。除非,我们在后端增加额外的处理逻辑。
+
+### 2.有效避免了CSRF 攻击
+
+**CSRF(Cross Site Request Forgery)**一般被翻译为 **跨站请求伪造**,属于网络攻击领域范围。相比于 SQL 脚本注入、XSS等等安全攻击方式,CSRF 的知名度并没有它们高。但是,它的确是每个系统都要考虑的安全隐患,就连技术帝国 Google 的 Gmail 在早些年也被曝出过存在 CSRF 漏洞,这给 Gmail 的用户造成了很大的损失。
+
+那么究竟什么是 **跨站请求伪造** 呢?说简单用你的身份去发送一些对你不友好的请求。举个简单的例子:
+
+小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了10000元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。
+
+```html
+科学理财,年盈利率过万>
+```
+
+导致这个问题很大的原因就是: Session 认证中 Cookie 中的 session_id 是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。
+
+**那为什么 token 不会存在这种问题呢?**
+
+我是这样理解的:一般情况下我们使用 JWT 的话,在我们登录成功获得 token 之后,一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。
+
+但是这样会存在 XSS 攻击中被盗的风险,为了避免 XSS 攻击,你可以选择将 token 存储在标记为`httpOnly` 的cookie 中。但是,这样又导致了你必须自己提供CSRF保护。
+
+具体采用上面哪两种方式存储 token 呢,大部分情况下存放在 local storage 下都是最好的选择,某些情况下可能需要存放在标记为`httpOnly` 的cookie 中会更好。
+
+### 3.适合移动端应用
+
+使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以不适合移动端。
+
+但是,使用 token 进行身份认证就不会存在这种问题,因为只要 token 可以被客户端存储就能够使用,而且 token 还可以跨语言使用。
+
+### 4.单点登录友好
+
+使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 token 进行认证的话, token 被保存在客户端,不会存在这些问题。
+
+## Token 认证常见问题以及解决办法
+
+### 1.注销登录等场景下 token 还有效
+
+与之类似的具体相关场景有:
+
+1. 退出登录;
+2. 修改密码;
+3. 服务端修改了某个用户具有的权限或者角色;
+4. 用户的帐户被删除/暂停。
+5. 用户由管理员注销;
+
+这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 token 认证的方式就不好解决了。我们也说过了,token 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。那么,我们如何解决这个问题呢?查阅了很多资料,总结了下面几种方案:
+
+- **将 token 存入内存数据库**:将 token 存入 DB 中,redis 内存数据库在这里是是不错的选择。如果需要让某个 token 失效就直接从 redis 中删除这个 token 即可。但是,这样会导致每次使用 token 发送请求都要先从 DB 中查询 token 是否存在的步骤,而且违背了 JWT 的无状态原则。
+- **黑名单机制**:和上面的方式类似,使用内存数据库比如 redis 维护一个黑名单,如果想让某个 token 失效的话就直接将这个 token 加入到 **黑名单** 即可。然后,每次使用 token 进行请求的话都会先判断这个 token 是否存在于黑名单中。
+- **修改密钥 (Secret)** : 我们为每个用户都创建一个专属密钥,如果我们想让某个 token 失效,我们直接修改对应用户的密钥即可。但是,这样相比于前两种引入内存数据库带来了危害更大,比如:1⃣️如果服务是分布式的,则每次发出新的 token 时都必须在多台机器同步密钥。为此,你需要将必须将机密存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别了。2⃣️如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
+- **保持令牌的有效期限短并经常轮换** :很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。
+
+对于修改密码后 token 还有效问题的解决还是比较容易的,说一种我觉得比较好的方式:**使用用户的密码的哈希值对 token 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。**
+
+### 2.token 的续签问题
+
+token 有效期一般都建议设置的不太长,那么 token 过期后如何认证,如何实现动态刷新 token,避免用户经常需要重新登录?
+
+我们先来看看在 Session 认证中一般的做法:**假如 session 的有效期30分钟,如果 30 分钟内用户有访问,就把 session 有效期被延长30分钟。**
+
+1. **类似于 Session 认证中的做法**:这种方案满足于大部分场景。假设服务端给的 token 有效期设置为30分钟,服务端每次进行校验时,如果发现 token 的有效期马上快过期了,服务端就重新生成 token 给客户端。客户端每次请求都检查新旧token,如果不一致,则更新本地的token。这种做法的问题是仅仅在快过期的时候请求才会更新 token ,对客户端不是很友好。
+2. **每次请求都返回新 token** :这种方案的的思路很简单,但是,很明显,开销会比较大。
+3. **token 有效期设置到半夜** :这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
+4. **用户登录返回两个 token** :第一个是 acessToken ,它的过期时间 token 本身的过期时间比如半个小时,另外一个是 refreshToken 它的过期时间更长一点比如为1天。客户端登录后,将 accessToken和refreshToken 保存在本地,每次访问将 accessToken 传给服务端。服务端校验 accessToken 的有效性,如果过期的话,就将 refreshToken 传给服务端。如果有效,服务端就生成新的 accessToken 给客户端。否则,客户端就重新登录即可。该方案的不足是:1⃣️需要客户端来配合;2⃣️用户注销的时候需要同时保证两个 token 都无效;3⃣️重新请求获取 token 的过程中会有短暂 token 不可用的情况(可以通过在客户端设置定时器,当accessToken 快过期的时候,提前去通过 refreshToken 获取新的accessToken)。
+
+## 总结
+
+JWT 最适合的场景是不需要服务端保存用户状态的场景,比如如果考虑到 token 注销和 token 续签的场景话,没有特别好的解决方案,大部分解决方案都给 token 加上了状态,这就有点类似 Session 认证了。
+
+## Reference
+
+- [JWT 超详细分析](https://learnku.com/articles/17883?order_by=vote_count&)
+- https://medium.com/devgorilla/how-to-log-out-when-using-jwt-a8c7823e8a6
+- https://medium.com/@agungsantoso/csrf-protection-with-json-web-tokens-83e0f2fcbcc
+- [Invalidating JSON Web Tokens](https://stackoverflow.com/questions/21978658/invalidating-json-web-tokens)
+
diff --git a/docs/system-design/authority-certification/basis-of-authority-certification.md b/docs/system-design/authority-certification/basis-of-authority-certification.md
new file mode 100644
index 00000000..3080ebcf
--- /dev/null
+++ b/docs/system-design/authority-certification/basis-of-authority-certification.md
@@ -0,0 +1,218 @@
+## 1. 认证 (Authentication) 和授权 (Authorization)的区别是什么?
+
+这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。
+
+说简单点就是:
+
+**认证 (Authentication):** 你是谁。
+
+
+
+**授权 (Authorization):** 你有权限干什么。
+
+
+
+稍微正式点(啰嗦点)的说法就是:
+
+- **Authentication(认证)** 是验证您的身份的凭据(例如用户名/用户ID和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
+- **Authorization(授权)** 发生在 **Authentication(认证)** 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。
+
+这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。
+
+## 2. 什么是Cookie ? Cookie的作用是什么?如何在服务端使用 Cookie ?
+
+
+
+### 2.1 什么是Cookie ? Cookie的作用是什么?
+
+Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
+
+维基百科是这样定义 Cookie 的:Cookies是某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。简单来说: **Cookie 存放在客户端,一般用来保存用户信息**。
+
+下面是 Cookie 的一些应用案例:
+
+1. 我们在 Cookie 中保存已经登录过的用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了。除此之外,Cookie 还能保存用户首选项,主题和其他设置信息。
+2. 使用Cookie 保存 session 或者 token ,向后端发送请求的时候带上 Cookie,这样后端就能取到session或者token了。这样就能记录用户当前的状态了,因为 HTTP 协议是无状态的。
+3. Cookie 还可以用来记录和分析用户行为。举个简单的例子你在网上购物的时候,因为HTTP协议是没有状态的,如果服务器想要获取你在某个页面的停留状态或者看了哪些商品,一种常用的实现方式就是将这些信息存放在Cookie
+
+### 2.2 如何在服务端使用 Cookie 呢?
+
+这部分内容参考:https://attacomsian.com/blog/cookies-spring-boot,更多如何在Spring Boot中使用Cookie 的内容可以查看这篇文章。
+
+**1)设置cookie返回给客户端**
+
+```java
+@GetMapping("/change-username")
+public String setCookie(HttpServletResponse response) {
+ // 创建一个 cookie
+ Cookie cookie = new Cookie("username", "Jovan");
+ //设置 cookie过期时间
+ cookie.setMaxAge(7 * 24 * 60 * 60); // expires in 7 days
+ //添加到 response 中
+ response.addCookie(cookie);
+
+ return "Username is changed!";
+}
+```
+
+**2) 使用Spring框架提供的`@CookieValue`注解获取特定的 cookie的值**
+
+```java
+@GetMapping("/")
+public String readCookie(@CookieValue(value = "username", defaultValue = "Atta") String username) {
+ return "Hey! My username is " + username;
+}
+```
+
+**3) 读取所有的 Cookie 值**
+
+```java
+@GetMapping("/all-cookies")
+public String readAllCookies(HttpServletRequest request) {
+
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ return Arrays.stream(cookies)
+ .map(c -> c.getName() + "=" + c.getValue()).collect(Collectors.joining(", "));
+ }
+
+ return "No cookies";
+}
+```
+
+## 3. Cookie 和 Session 有什么区别?如何使用Session进行身份验证?
+
+**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
+
+**Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。**
+
+**那么,如何使用Session进行身份验证?**
+
+很多时候我们都是通过 SessionID 来实现特定的用户,SessionID 一般会选择存放在 Redis 中。举个例子:用户成功登陆系统,然后返回给客户端具有 SessionID 的 Cookie,当用户向后端发起请求的时候会把 SessionID 带上,这样后端就知道你的身份状态了。关于这种认证方式更详细的过程如下:
+
+
+
+1. 用户向服务器发送用户名和密码用于登陆系统。
+2. 服务器验证通过后,服务器为用户创建一个 Session,并将 Session信息存储 起来。
+3. 服务器向用户返回一个 SessionID,写入用户的 Cookie。
+4. 当用户保持登录状态时,Cookie 将与每个后续请求一起被发送出去。
+5. 服务器可以将存储在 Cookie 上的 Session ID 与存储在内存中或者数据库中的 Session 信息进行比较,以验证用户的身份,返回给用户客户端响应信息的时候会附带用户当前的状态。
+
+使用 Session 的时候需要注意下面几个点:
+
+1. 依赖Session的关键业务一定要确保客户端开启了Cookie。
+2. 注意Session的过期时间
+
+花了个图简单总结了一下Session认证涉及的一些东西。
+
+
+
+另外,Spring Session提供了一种跨多个应用程序或实例管理用户会话信息的机制。如果想详细了解可以查看下面几篇很不错的文章:
+
+- [Getting Started with Spring Session](https://codeboje.de/spring-session-tutorial/)
+- [Guide to Spring Session](https://www.baeldung.com/spring-session)
+- [Sticky Sessions with Spring Session & Redis](https://medium.com/@gvnix/sticky-sessions-with-spring-session-redis-bdc6f7438cc3)
+
+## 4.如果没有Cookie的话Session还能用吗?
+
+这是一道经典的面试题!
+
+一般是通过 Cookie 来保存 SessionID ,假如你使用了 Cookie 保存 SessionID的方案的话, 如果客户端禁用了Cookie,那么Seesion就无法正常工作。
+
+但是,并不是没有 Cookie 之后就不能用 Session 了,比如你可以将SessionID放在请求的 url 里面`https://javaguide.cn/?session_id=xxx` 。这种方案的话可行,但是安全性和用户体验感降低。当然,为了你也可以对 SessionID 进行一次加密之后再传入后端。
+
+## 5.为什么Cookie 无法防止CSRF攻击,而token可以?
+
+**CSRF(Cross Site Request Forgery)**一般被翻译为 **跨站请求伪造** 。那么什么是 **跨站请求伪造** 呢?说简单用你的身份去发送一些对你不友好的请求。举个简单的例子:
+
+小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了10000元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。
+
+```
+ 科学理财,年盈利率过万>
+```
+
+上面也提到过,进行Session 认证的时候,我们一般使用 Cookie 来存储 SessionId,当我们登陆后后端生成一个SessionId放在Cookie中返回给客户端,服务端通过Redis或者其他存储工具记录保存着这个Sessionid,客户端登录以后每次请求都会带上这个SessionId,服务端通过这个SessionId来标示你这个人。如果别人通过 cookie拿到了 SessionId 后就可以代替你的身份访问系统了。
+
+ Session 认证中 Cookie 中的 SessionId是由浏览器发送到服务端的,借助这个特性,攻击者就可以通过让用户误点攻击链接,达到攻击效果。
+
+但是,我们使用 token 的话就不会存在这个问题,在我们登录成功获得 token 之后,一般会选择存放在 local storage 中。然后我们在前端通过某些方式会给每个发到后端的请求加上这个 token,这样就不会出现 CSRF 漏洞的问题。因为,即使有个你点击了非法链接发送了请求到服务端,这个非法请求是不会携带 token 的,所以这个请求将是非法的。
+
+需要注意的是不论是 Cookie 还是 token 都无法避免跨站脚本攻击(Cross Site Scripting)XSS。
+
+> 跨站脚本攻击(Cross Site Scripting)缩写为 CSS 但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。
+
+XSS中攻击者会用各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本盗用信息比如cookie。
+
+推荐阅读:
+
+1. [如何防止CSRF攻击?—美团技术团队](https://tech.meituan.com/2018/10/11/fe-security-csrf.html)
+
+## 6. 什么是 Token?什么是 JWT?如何基于Token进行身份验证?
+
+我们在上一个问题中探讨了使用 Session 来鉴别用户的身份,并且给出了几个 Spring Session 的案例分享。 我们知道 Session 信息需要保存一份在服务器端。这种方式会带来一些麻烦,比如需要我们保证保存 Session 信息服务器的可用性、不适合移动端(依赖Cookie)等等。
+
+有没有一种不需要自己存放 Session 信息就能实现身份验证的方式呢?使用 Token 即可!JWT (JSON Web Token) 就是这种方式的实现,通过这种方式服务器端就不需要保存 Session 数据了,只用在客户端保存服务端返回给客户的 Token 就可以了,扩展性得到提升。
+
+**JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。**
+
+下面是 [RFC 7519](https://tools.ietf.org/html/rfc7519) 对 JWT 做的较为正式的定义。
+
+> JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——[JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
+
+JWT 由 3 部分构成:
+
+1. Header :描述 JWT 的元数据。定义了生成签名的算法以及 Token 的类型。
+2. Payload(负载):用来存放实际需要传递的数据
+3. Signature(签名):服务器通过`Payload`、`Header`和一个密钥(`secret`)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。
+
+在基于 Token 进行身份验证的的应用程序中,服务器通过`Payload`、`Header`和一个密钥(`secret`)创建令牌(`Token`)并将 `Token` 发送给客户端,客户端将 `Token` 保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization字段中:` Authorization: Bearer Token`。
+
+
+
+1. 用户向服务器发送用户名和密码用于登陆系统。
+2. 身份验证服务响应并返回了签名的 JWT,上面包含了用户是谁的内容。
+3. 用户以后每次向后端发请求都在Header中带上 JWT。
+4. 服务端检查 JWT 并从中获取用户相关信息。
+
+
+推荐阅读:
+
+- [JWT (JSON Web Tokens) Are Better Than Session Cookies](https://dzone.com/articles/jwtjson-web-tokens-are-better-than-session-cookies)
+- [JSON Web Tokens (JWT) 与 Sessions](https://juejin.im/entry/577b7b56a3413100618c2938)
+- [JSON Web Token 入门教程](https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)
+- [彻底理解Cookie,Session,Token](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485603&idx=1&sn=c8d324f44d6102e7b44554733da10bb7&chksm=cea24768f9d5ce7efe7291ddabce02b68db34073c7e7d9a7dc9a7f01c5a80cebe33ac75248df&token=844918801&lang=zh_CN#rd)
+
+## 7 什么是OAuth 2.0?
+
+OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。而 OAuth 2.0是对 OAuth 1.0 的完全重新设计,OAuth 2.0更快,更容易实现,OAuth 1.0 已经被废弃。详情请见:[rfc6749](https://tools.ietf.org/html/rfc6749)。
+
+实际上它就是一种授权机制,它的最终目的是为第三方应用颁发一个有时效性的令牌 token,使得第三方应用能够通过该令牌获取相关的资源。
+
+OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录的时候一般就是使用的 OAuth 2.0 协议。
+
+另外,现在OAuth 2.0也常见于支付场景(微信支付、支付宝支付)和开发平台(微信开放平台、阿里开放平台等等)。
+
+微信支付账户相关参数:
+
+
+
+**推荐阅读:**
+
+- [OAuth 2.0 的一个简单解释](http://www.ruanyifeng.com/blog/2019/04/oauth_design.html)
+- [10 分钟理解什么是 OAuth 2.0 协议](https://deepzz.com/post/what-is-oauth2-protocol.html)
+- [OAuth 2.0 的四种方式](http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html)
+- [GitHub OAuth 第三方登录示例教程](http://www.ruanyifeng.com/blog/2019/04/github-oauth.html)
+
+## 8 什么是 SSO?
+
+SSO(Single Sign On)即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了京东金融之后,我们同时也成功登陆京东的京东超市、京东家电等子系统。
+
+## 9.SSO与OAuth2.0的区别
+
+OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。SSO解决的是一个公司的多个相关的自系统的之间的登陆问题比如京东旗下相关子系统京东金融、京东超市、京东家电等等。
+
+## 参考
+
+- https://medium.com/@sherryhsu/session-vs-token-based-authentication-11a6c5ac45e4
+- https://www.varonis.com/blog/what-is-oauth/
+- https://tools.ietf.org/html/rfc6749
diff --git a/docs/system-design/authority-certification/sso.md b/docs/system-design/authority-certification/sso.md
new file mode 100644
index 00000000..a2042c61
--- /dev/null
+++ b/docs/system-design/authority-certification/sso.md
@@ -0,0 +1,125 @@
+
+
+> 本文授权转载自 : https://ken.io/note/sso-design-implement 作者:ken.io
+>
+> 相关推荐阅读:**[系统的讲解 - SSO单点登录](https://www.imooc.com/article/286710)**
+
+## 一、前言
+
+### 1、SSO说明
+
+SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。https://baike.baidu.com/item/SSO/3451380
+
+例如访问在网易账号中心(http://reg.163.com/ )登录之后
+访问以下站点都是登录状态
+
+- 网易直播 [http://v.163.com](http://v.163.com/)
+- 网易博客 [http://blog.163.com](http://blog.163.com/)
+- 网易花田 [http://love.163.com](http://love.163.com/)
+- 网易考拉 [https://www.kaola.com](https://www.kaola.com/)
+- 网易Lofter [http://www.lofter.com](http://www.lofter.com/)
+
+### 2、单点登录系统的好处
+
+1. **用户角度** :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
+2. **系统管理员角度** : 管理员只需维护好一个统一的账号中心就可以了,方便。
+3. **新系统开发角度:** 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
+
+### 3、设计目标
+
+本篇文章也主要是为了探讨如何设计&实现一个SSO系统
+
+以下为需要实现的核心功能:
+
+- 单点登录
+- 单点登出
+- 支持跨域单点登录
+- 支持跨域单点登出
+
+## 二、SSO设计与实现
+
+### 1、核心应用与依赖
+
+
+
+| 应用/模块/对象 | 说明 |
+| ---------------- | ----------------------------------- |
+| 前台站点 | 需要登录的站点 |
+| SSO站点-登录 | 提供登录的页面 |
+| SSO站点-登出 | 提供注销登录的入口 |
+| SSO服务-登录 | 提供登录服务 |
+| SSO服务-登录状态 | 提供登录状态校验/登录信息查询的服务 |
+| SSO服务-登出 | 提供用户注销登录的服务 |
+| 数据库 | 存储用户账户信息 |
+| 缓存 | 存储用户的登录信息,通常使用Redis |
+
+### 2、用户登录状态的存储与校验
+
+常见的Web框架对于[Session](https://ken.io/note/session-principle-skill)的实现都是生成一个SessionId存储在浏览器Cookie中。然后将Session内容存储在服务器端内存中,这个 ken.io 在之前[Session工作原理](https://ken.io/note/session-principle-skill)中也提到过。整体也是借鉴这个思路。
+用户登录成功之后,生成AuthToken交给客户端保存。如果是浏览器,就保存在Cookie中。如果是手机App就保存在App本地缓存中。本篇主要探讨基于Web站点的SSO。
+用户在浏览需要登录的页面时,客户端将AuthToken提交给SSO服务校验登录状态/获取用户登录信息
+
+对于登录信息的存储,建议采用Redis,使用Redis集群来存储登录信息,既可以保证高可用,又可以线性扩充。同时也可以让SSO服务满足负载均衡/可伸缩的需求。
+
+| 对象 | 说明 |
+| --------- | ------------------------------------------------------------ |
+| AuthToken | 直接使用UUID/GUID即可,如果有验证AuthToken合法性需求,可以将UserName+时间戳加密生成,服务端解密之后验证合法性 |
+| 登录信息 | 通常是将UserId,UserName缓存起来 |
+
+### 3、用户登录/登录校验
+
+- 登录时序图
+
+
+
+按照上图,用户登录后Authtoken保存在Cookie中。 domian= test. com
+浏览器会将domain设置成 .test.com,
+这样访问所有*.test.com的web站点,都会将Authtoken携带到服务器端。
+然后通过SSO服务,完成对用户状态的校验/用户登录信息的获取
+
+- 登录信息获取/登录状态校验
+
+
+
+### 4、用户登出
+
+用户登出时要做的事情很简单:
+
+1. 服务端清除缓存(Redis)中的登录状态
+2. 客户端清除存储的AuthToken
+
+- 登出时序图
+
+
+
+### 5、跨域登录、登出
+
+前面提到过,核心思路是客户端存储AuthToken,服务器端通过Redis存储登录信息。由于客户端是将AuthToken存储在Cookie中的。所以跨域要解决的问题,就是如何解决Cookie的跨域读写问题。
+
+> **Cookie是不能跨域的** ,比如我一个
+
+解决跨域的核心思路就是:
+
+- 登录完成之后通过回调的方式,将AuthToken传递给主域名之外的站点,该站点自行将AuthToken保存在当前域下的Cookie中。
+- 登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置Cookie中的AuthToken过期的操作。
+- 跨域登录(主域名已登录)
+
+
+
+- 跨域登录(主域名未登录)
+
+
+
+- 跨域登出
+
+
+
+## 三、备注
+
+- 关于方案
+
+这次设计方案更多是提供实现思路。如果涉及到APP用户登录等情况,在访问SSO服务时,增加对APP的签名验证就好了。当然,如果有无线网关,验证签名不是问题。
+
+- 关于时序图
+
+时序图中并没有包含所有场景,ken.io只列举了核心/主要场景,另外对于一些不影响理解思路的消息能省就省了。
\ No newline at end of file
diff --git a/docs/system-design/data-communication/Kafka入门看这一篇就够了.md b/docs/system-design/data-communication/Kafka入门看这一篇就够了.md
new file mode 100644
index 00000000..cd45e064
--- /dev/null
+++ b/docs/system-design/data-communication/Kafka入门看这一篇就够了.md
@@ -0,0 +1,321 @@
+> 本文由 JavaGuide 读者推荐,JavaGuide 对文章进行了整理排版!原文地址:https://www.wmyskxz.com/2019/07/17/kafka-ru-men-jiu-zhe-yi-pian/ , 作者:我没有三颗心脏。
+
+# 一、Kafka 简介
+
+------
+
+## Kafka 创建背景
+
+**Kafka** 是一个消息系统,原本开发自 LinkedIn,用作 LinkedIn 的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。现在它已被[多家不同类型的公司](https://cwiki.apache.org/confluence/display/KAFKA/Powered+By) 作为多种类型的数据管道和消息系统使用。
+
+**活动流数据**是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。活动数据包括页面访问量(Page View)、被查看内容方面的信息以及搜索情况等内容。这种数据通常的处理方式是先把各种活动以日志的形式写入某种文件,然后周期性地对这些文件进行统计分析。**运营数据**指的是服务器的性能数据(CPU、IO 使用率、请求时间、服务日志等等数据)。运营数据的统计方法种类繁多。
+
+近年来,活动和运营数据处理已经成为了网站软件产品特性中一个至关重要的组成部分,这就需要一套稍微更加复杂的基础设施对其提供支持。
+
+## Kafka 简介
+
+**Kafka 是一种分布式的,基于发布 / 订阅的消息系统。**
+
+主要设计目标如下:
+
+- 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能。
+- 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输。
+- 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输。
+- 同时支持离线数据处理和实时数据处理。
+- Scale out:支持在线水平扩展。
+
+## Kafka 基础概念
+
+### 概念一:生产者与消费者
+
+
+
+对于 Kafka 来说客户端有两种基本类型:
+
+1. **生产者(Producer)**
+2. **消费者(Consumer)**。
+
+除此之外,还有用来做数据集成的 Kafka Connect API 和流式处理的 Kafka Streams 等高阶客户端,但这些高阶客户端底层仍然是生产者和消费者API,它们只不过是在上层做了封装。
+
+这很容易理解,生产者(也称为发布者)创建消息,而消费者(也称为订阅者)负责消费or读取消息。
+
+### 概念二:主题(Topic)与分区(Partition)
+
+
+
+在 Kafka 中,消息以**主题(Topic)**来分类,每一个主题都对应一个 **「消息队列」**,这有点儿类似于数据库中的表。但是如果我们把所有同类的消息都塞入到一个“中心”队列中,势必缺少可伸缩性,无论是生产者/消费者数目的增加,还是消息数量的增加,都可能耗尽系统的性能或存储。
+
+我们使用一个生活中的例子来说明:现在 A 城市生产的某商品需要运输到 B 城市,走的是公路,那么单通道的高速公路不论是在「A 城市商品增多」还是「现在 C 城市也要往 B 城市运输东西」这样的情况下都会出现「吞吐量不足」的问题。所以我们现在引入**分区(Partition)**的概念,类似“允许多修几条道”的方式对我们的主题完成了水平扩展。
+
+### 概念三:Broker 和集群(Cluster)
+
+一个 Kafka 服务器也称为 Broker,它接受生产者发送的消息并存入磁盘;Broker 同时服务消费者拉取分区消息的请求,返回目前已经提交的消息。使用特定的机器硬件,一个 Broker 每秒可以处理成千上万的分区和百万量级的消息。(现在动不动就百万量级..我特地去查了一把,好像确实集群的情况下吞吐量挺高的..嗯..)
+
+若干个 Broker 组成一个集群(Cluster),其中集群内某个 Broker 会成为集群控制器(Cluster Controller),它负责管理集群,包括分配分区到 Broker、监控 Broker 故障等。在集群内,一个分区由一个 Broker 负责,这个 Broker 也称为这个分区的 Leader;当然一个分区可以被复制到多个 Broker 上来实现冗余,这样当存在 Broker 故障时可以将其分区重新分配到其他 Broker 来负责。下图是一个样例:
+
+
+
+Kafka 的一个关键性质是日志保留(retention),我们可以配置主题的消息保留策略,譬如只保留一段时间的日志或者只保留特定大小的日志。当超过这些限制时,老的消息会被删除。我们也可以针对某个主题单独设置消息过期策略,这样对于不同应用可以实现个性化。
+
+### 概念四:多集群
+
+随着业务发展,我们往往需要多集群,通常处于下面几个原因:
+
+- 基于数据的隔离;
+- 基于安全的隔离;
+- 多数据中心(容灾)
+
+当构建多个数据中心时,往往需要实现消息互通。举个例子,假如用户修改了个人资料,那么后续的请求无论被哪个数据中心处理,这个更新需要反映出来。又或者,多个数据中心的数据需要汇总到一个总控中心来做数据分析。
+
+上面说的分区复制冗余机制只适用于同一个 Kafka 集群内部,对于多个 Kafka 集群消息同步可以使用 Kafka 提供的 MirrorMaker 工具。本质上来说,MirrorMaker 只是一个 Kafka 消费者和生产者,并使用一个队列连接起来而已。它从一个集群中消费消息,然后往另一个集群生产消息。
+
+
+# 二、Kafka 的设计与实现
+
+------
+
+上面我们知道了 Kafka 中的一些基本概念,但作为一个成熟的「消息队列」中间件,其中有许多有意思的设计值得我们思考,下面我们简单列举一些。
+
+## 讨论一:Kafka 存储在文件系统上
+
+是的,**您首先应该知道 Kafka 的消息是存在于文件系统之上的**。Kafka 高度依赖文件系统来存储和缓存消息,一般的人认为 “磁盘是缓慢的”,所以对这样的设计持有怀疑态度。实际上,磁盘比人们预想的快很多也慢很多,这取决于它们如何被使用;一个好的磁盘结构设计可以使之跟网络速度一样快。
+
+现代的操作系统针对磁盘的读写已经做了一些优化方案来加快磁盘的访问速度。比如,**预读**会提前将一个比较大的磁盘快读入内存。**后写**会将很多小的逻辑写操作合并起来组合成一个大的物理写操作。并且,操作系统还会将主内存剩余的所有空闲内存空间都用作**磁盘缓存**,所有的磁盘读写操作都会经过统一的磁盘缓存(除了直接 I/O 会绕过磁盘缓存)。综合这几点优化特点,**如果是针对磁盘的顺序访问,某些情况下它可能比随机的内存访问都要快,甚至可以和网络的速度相差无几。**
+
+**上述的 Topic 其实是逻辑上的概念,面相消费者和生产者,物理上存储的其实是 Partition**,每一个 Partition 最终对应一个目录,里面存储所有的消息和索引文件。默认情况下,每一个 Topic 在创建时如果不指定 Partition 数量时只会创建 1 个 Partition。比如,我创建了一个 Topic 名字为 test ,没有指定 Partition 的数量,那么会默认创建一个 test-0 的文件夹,这里的命名规则是:`-`。
+
+
+
+任何发布到 Partition 的消息都会被追加到 Partition 数据文件的尾部,这样的顺序写磁盘操作让 Kafka 的效率非常高(经验证,顺序写磁盘效率比随机写内存还要高,这是 Kafka 高吞吐率的一个很重要的保证)。
+
+每一条消息被发送到 Broker 中,会根据 Partition 规则选择被存储到哪一个 Partition。如果 Partition 规则设置的合理,所有消息可以均匀分布到不同的 Partition中。
+
+## 讨论二:Kafka 中的底层存储设计
+
+假设我们现在 Kafka 集群只有一个 Broker,我们创建 2 个 Topic 名称分别为:「topic1」和「topic2」,Partition 数量分别为 1、2,那么我们的根目录下就会创建如下三个文件夹:
+
+```shell
+ | --topic1-0
+ | --topic2-0
+ | --topic2-1
+```
+
+在 Kafka 的文件存储中,同一个 Topic 下有多个不同的 Partition,每个 Partition 都为一个目录,而每一个目录又被平均分配成多个大小相等的 **Segment File** 中,Segment File 又由 index file 和 data file 组成,他们总是成对出现,后缀 “.index” 和 “.log” 分表表示 Segment 索引文件和数据文件。
+
+现在假设我们设置每个 Segment 大小为 500 MB,并启动生产者向 topic1 中写入大量数据,topic1-0 文件夹中就会产生类似如下的一些文件:
+
+```shell
+ | --topic1-0
+ | --00000000000000000000.index
+ | --00000000000000000000.log
+ | --00000000000000368769.index
+ | --00000000000000368769.log
+ | --00000000000000737337.index
+ | --00000000000000737337.log
+ | --00000000000001105814.index | --00000000000001105814.log
+ | --topic2-0
+ | --topic2-1
+
+```
+
+**Segment 是 Kafka 文件存储的最小单位。**Segment 文件命名规则:Partition 全局的第一个 Segment 从 0 开始,后续每个 Segment 文件名为上一个 Segment 文件最后一条消息的 offset 值。数值最大为 64 位 long 大小,19 位数字字符长度,没有数字用0填充。如 00000000000000368769.index 和 00000000000000368769.log。
+
+以上面的一对 Segment File 为例,说明一下索引文件和数据文件对应关系:
+
+
+
+
+
+其中以索引文件中元数据 `<3, 497>` 为例,依次在数据文件中表示第 3 个 message(在全局 Partition 表示第 368769 + 3 = 368772 个 message)以及该消息的物理偏移地址为 497。
+
+注意该 index 文件并不是从0开始,也不是每次递增1的,这是因为 Kafka 采取稀疏索引存储的方式,每隔一定字节的数据建立一条索引,它减少了索引文件大小,使得能够把 index 映射到内存,降低了查询时的磁盘 IO 开销,同时也并没有给查询带来太多的时间消耗。
+
+因为其文件名为上一个 Segment 最后一条消息的 offset ,所以当需要查找一个指定 offset 的 message 时,通过在所有 segment 的文件名中进行二分查找就能找到它归属的 segment ,再在其 index 文件中找到其对应到文件上的物理位置,就能拿出该 message 。
+
+由于消息在 Partition 的 Segment 数据文件中是顺序读写的,且消息消费后不会删除(删除策略是针对过期的 Segment 文件),这种顺序磁盘 IO 存储设计师 Kafka 高性能很重要的原因。
+
+> Kafka 是如何准确的知道 message 的偏移的呢?这是因为在 Kafka 定义了标准的数据存储结构,在 Partition 中的每一条 message 都包含了以下三个属性:
+>
+> - offset:表示 message 在当前 Partition 中的偏移量,是一个逻辑上的值,唯一确定了 Partition 中的一条 message,可以简单的认为是一个 id;
+> - MessageSize:表示 message 内容 data 的大小;
+> - data:message 的具体内容
+
+## 讨论三:生产者设计概要
+
+当我们发送消息之前,先问几个问题:每条消息都是很关键且不能容忍丢失么?偶尔重复消息可以么?我们关注的是消息延迟还是写入消息的吞吐量?
+
+举个例子,有一个信用卡交易处理系统,当交易发生时会发送一条消息到 Kafka,另一个服务来读取消息并根据规则引擎来检查交易是否通过,将结果通过 Kafka 返回。对于这样的业务,消息既不能丢失也不能重复,由于交易量大因此吞吐量需要尽可能大,延迟可以稍微高一点。
+
+再举个例子,假如我们需要收集用户在网页上的点击数据,对于这样的场景,少量消息丢失或者重复是可以容忍的,延迟多大都不重要只要不影响用户体验,吞吐则根据实时用户数来决定。
+
+不同的业务需要使用不同的写入方式和配置。具体的方式我们在这里不做讨论,现在先看下生产者写消息的基本流程:
+
+
+
+图片来源:[http://www.dengshenyu.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/11/12/kafka-producer.html](http://www.dengshenyu.com/分布式系统/2017/11/12/kafka-producer.html)
+
+流程如下:
+
+1. 首先,我们需要创建一个ProducerRecord,这个对象需要包含消息的主题(topic)和值(value),可以选择性指定一个键值(key)或者分区(partition)。
+2. 发送消息时,生产者会对键值和值序列化成字节数组,然后发送到分配器(partitioner)。
+3. 如果我们指定了分区,那么分配器返回该分区即可;否则,分配器将会基于键值来选择一个分区并返回。
+4. 选择完分区后,生产者知道了消息所属的主题和分区,它将这条记录添加到相同主题和分区的批量消息中,另一个线程负责发送这些批量消息到对应的Kafka broker。
+5. 当broker接收到消息后,如果成功写入则返回一个包含消息的主题、分区及位移的RecordMetadata对象,否则返回异常。
+6. 生产者接收到结果后,对于异常可能会进行重试。
+
+
+
+## 讨论四:消费者设计概要
+
+### 消费者与消费组
+
+假设这么个场景:我们从Kafka中读取消息,并且进行检查,最后产生结果数据。我们可以创建一个消费者实例去做这件事情,但如果生产者写入消息的速度比消费者读取的速度快怎么办呢?这样随着时间增长,消息堆积越来越严重。对于这种场景,我们需要增加多个消费者来进行水平扩展。
+
+Kafka消费者是**消费组**的一部分,当多个消费者形成一个消费组来消费主题时,每个消费者会收到不同分区的消息。假设有一个T1主题,该主题有4个分区;同时我们有一个消费组G1,这个消费组只有一个消费者C1。那么消费者C1将会收到这4个分区的消息,如下所示:
+
+
+如果我们增加新的消费者C2到消费组G1,那么每个消费者将会分别收到两个分区的消息,如下所示:
+
+
+
+如果增加到4个消费者,那么每个消费者将会分别收到一个分区的消息,如下所示:
+
+
+
+但如果我们继续增加消费者到这个消费组,剩余的消费者将会空闲,不会收到任何消息:
+
+
+
+总而言之,我们可以通过增加消费组的消费者来进行水平扩展提升消费能力。这也是为什么建议创建主题时使用比较多的分区数,这样可以在消费负载高的情况下增加消费者来提升性能。另外,消费者的数量不应该比分区数多,因为多出来的消费者是空闲的,没有任何帮助。
+
+**Kafka一个很重要的特性就是,只需写入一次消息,可以支持任意多的应用读取这个消息。**换句话说,每个应用都可以读到全量的消息。为了使得每个应用都能读到全量消息,应用需要有不同的消费组。对于上面的例子,假如我们新增了一个新的消费组G2,而这个消费组有两个消费者,那么会是这样的:
+
+
+
+在这个场景中,消费组G1和消费组G2都能收到T1主题的全量消息,在逻辑意义上来说它们属于不同的应用。
+
+最后,总结起来就是:如果应用需要读取全量消息,那么请为该应用设置一个消费组;如果该应用消费能力不足,那么可以考虑在这个消费组里增加消费者。
+
+### 消费组与分区重平衡
+
+可以看到,当新的消费者加入消费组,它会消费一个或多个分区,而这些分区之前是由其他消费者负责的;另外,当消费者离开消费组(比如重启、宕机等)时,它所消费的分区会分配给其他分区。这种现象称为**重平衡(rebalance)**。重平衡是 Kafka 一个很重要的性质,这个性质保证了高可用和水平扩展。**不过也需要注意到,在重平衡期间,所有消费者都不能消费消息,因此会造成整个消费组短暂的不可用。**而且,将分区进行重平衡也会导致原来的消费者状态过期,从而导致消费者需要重新更新状态,这段期间也会降低消费性能。后面我们会讨论如何安全的进行重平衡以及如何尽可能避免。
+
+消费者通过定期发送心跳(hearbeat)到一个作为组协调者(group coordinator)的 broker 来保持在消费组内存活。这个 broker 不是固定的,每个消费组都可能不同。当消费者拉取消息或者提交时,便会发送心跳。
+
+如果消费者超过一定时间没有发送心跳,那么它的会话(session)就会过期,组协调者会认为该消费者已经宕机,然后触发重平衡。可以看到,从消费者宕机到会话过期是有一定时间的,这段时间内该消费者的分区都不能进行消息消费;通常情况下,我们可以进行优雅关闭,这样消费者会发送离开的消息到组协调者,这样组协调者可以立即进行重平衡而不需要等待会话过期。
+
+在 0.10.1 版本,Kafka 对心跳机制进行了修改,将发送心跳与拉取消息进行分离,这样使得发送心跳的频率不受拉取的频率影响。另外更高版本的 Kafka 支持配置一个消费者多长时间不拉取消息但仍然保持存活,这个配置可以避免活锁(livelock)。活锁,是指应用没有故障但是由于某些原因不能进一步消费。
+
+### Partition 与消费模型
+
+上面提到,Kafka 中一个 topic 中的消息是被打散分配在多个 Partition(分区) 中存储的, Consumer Group 在消费时需要从不同的 Partition 获取消息,那最终如何重建出 Topic 中消息的顺序呢?
+
+答案是:没有办法。Kafka 只会保证在 Partition 内消息是有序的,而不管全局的情况。
+
+下一个问题是:Partition 中的消息可以被(不同的 Consumer Group)多次消费,那 Partition中被消费的消息是何时删除的? Partition 又是如何知道一个 Consumer Group 当前消费的位置呢?
+
+无论消息是否被消费,除非消息到期 Partition 从不删除消息。例如设置保留时间为 2 天,则消息发布 2 天内任何 Group 都可以消费,2 天后,消息自动被删除。
+Partition 会为每个 Consumer Group 保存一个偏移量,记录 Group 消费到的位置。 如下图:
+
+
+
+
+
+### 为什么 Kafka 是 pull 模型
+
+消费者应该向 Broker 要数据(pull)还是 Broker 向消费者推送数据(push)?作为一个消息系统,Kafka 遵循了传统的方式,选择由 Producer 向 broker push 消息并由 Consumer 从 broker pull 消息。一些 logging-centric system,比如 Facebook 的[Scribe](https://github.com/facebookarchive/scribe)和 Cloudera 的[Flume](https://flume.apache.org/),采用 push 模式。事实上,push 模式和 pull 模式各有优劣。
+
+**push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。**push 模式的目标是尽可能以最快速度传递消息,但是这样很容易造成 Consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。**而 pull 模式则可以根据 Consumer 的消费能力以适当的速率消费消息。**
+
+**对于 Kafka 而言,pull 模式更合适。**pull 模式可简化 broker 的设计,Consumer 可自主控制消费消息的速率,同时 Consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。
+
+## 讨论五:Kafka 如何保证可靠性
+
+当我们讨论**可靠性**的时候,我们总会提到*保证**这个词语。可靠性保证是基础,我们基于这些基础之上构建我们的应用。比如关系型数据库的可靠性保证是ACID,也就是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
+
+Kafka 中的可靠性保证有如下四点:
+
+- 对于一个分区来说,它的消息是有序的。如果一个生产者向一个分区先写入消息A,然后写入消息B,那么消费者会先读取消息A再读取消息B。
+- 当消息写入所有in-sync状态的副本后,消息才会认为**已提交(committed)**。这里的写入有可能只是写入到文件系统的缓存,不一定刷新到磁盘。生产者可以等待不同时机的确认,比如等待分区主副本写入即返回,后者等待所有in-sync状态副本写入才返回。
+- 一旦消息已提交,那么只要有一个副本存活,数据不会丢失。
+- 消费者只能读取到已提交的消息。
+
+使用这些基础保证,我们构建一个可靠的系统,这时候需要考虑一个问题:究竟我们的应用需要多大程度的可靠性?可靠性不是无偿的,它与系统可用性、吞吐量、延迟和硬件价格息息相关,得此失彼。因此,我们往往需要做权衡,一味的追求可靠性并不实际。
+
+> 想了解更多戳这里:http://www.dengshenyu.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/11/21/kafka-data-delivery.html
+
+# 三、动手搭一个 Kafka
+
+通过上面的描述,我们已经大致了解到了「Kafka」是何方神圣了,现在我们开始尝试自己动手本地搭一个来实际体验一把。
+
+## 第一步:下载 Kafka
+
+这里以 Mac OS 为例,在安装了 Homebrew 的情况下执行下列代码:
+
+```shell
+brew install kafka
+```
+
+由于 Kafka 依赖了 Zookeeper,所以在下载的时候会自动下载。
+
+## 第二步:启动服务
+
+我们在启动之前首先需要修改 Kafka 的监听地址和端口为 `localhost:9092`:
+
+```shell
+vi /usr/local/etc/kafka/server.properties
+```
+
+
+然后修改成下图的样子:
+
+
+依次启动 Zookeeper 和 Kafka:
+
+```shell
+brew services start zookeeper
+brew services start kafka
+```
+
+然后执行下列语句来创建一个名字为 “test” 的 Topic:
+
+```shell
+kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
+```
+
+我们可以通过下列的命令查看我们的 Topic 列表:
+
+```shell
+kafka-topics --list --zookeeper localhost:2181
+```
+
+## 第三步:发送消息
+
+然后我们新建一个控制台,运行下列命令创建一个消费者关注刚才创建的 Topic:
+
+```shell
+kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning
+```
+
+用控制台往刚才创建的 Topic 中添加消息,并观察刚才创建的消费者窗口:
+
+```shel
+kafka-console-producer --broker-list localhost:9092 --topic test
+```
+
+能通过消费者窗口观察到正确的消息:
+
+
+
+# 参考资料
+
+------
+
+1. https://www.infoq.cn/article/kafka-analysis-part-1 - Kafka 设计解析(一):Kafka 背景及架构介绍
+2. [http://www.dengshenyu.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/11/06/kafka-Meet-Kafka.html](http://www.dengshenyu.com/分布式系统/2017/11/06/kafka-Meet-Kafka.html) - Kafka系列(一)初识Kafka
+3. https://lotabout.me/2018/kafka-introduction/ - Kafka 入门介绍
+4. https://www.zhihu.com/question/28925721 - Kafka 中的 Topic 为什么要进行分区? - 知乎
+5. https://blog.joway.io/posts/kafka-design-practice/ - Kafka 的设计与实践思考
+6. [http://www.dengshenyu.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/11/21/kafka-data-delivery.html](http://www.dengshenyu.com/分布式系统/2017/11/21/kafka-data-delivery.html) - Kafka系列(六)可靠的数据传输
+
+
diff --git a/docs/system-design/data-communication/RocketMQ-Questions.md b/docs/system-design/data-communication/RocketMQ-Questions.md
new file mode 100644
index 00000000..a41a4035
--- /dev/null
+++ b/docs/system-design/data-communication/RocketMQ-Questions.md
@@ -0,0 +1,214 @@
+本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
+
+
+- [1 单机版消息中心](#1-%E5%8D%95%E6%9C%BA%E7%89%88%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
+- [2 分布式消息中心](#2-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E4%B8%AD%E5%BF%83)
+ - [2.1 问题与解决](#21-%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3)
+ - [2.1.1 消息丢失的问题](#211-%E6%B6%88%E6%81%AF%E4%B8%A2%E5%A4%B1%E7%9A%84%E9%97%AE%E9%A2%98)
+ - [2.1.2 同步落盘怎么才能快](#212-%E5%90%8C%E6%AD%A5%E8%90%BD%E7%9B%98%E6%80%8E%E4%B9%88%E6%89%8D%E8%83%BD%E5%BF%AB)
+ - [2.1.3 消息堆积的问题](#213-%E6%B6%88%E6%81%AF%E5%A0%86%E7%A7%AF%E7%9A%84%E9%97%AE%E9%A2%98)
+ - [2.1.4 定时消息的实现](#214-%E5%AE%9A%E6%97%B6%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
+ - [2.1.5 顺序消息的实现](#215-%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
+ - [2.1.6 分布式消息的实现](#216-%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E7%9A%84%E5%AE%9E%E7%8E%B0)
+ - [2.1.7 消息的 push 实现](#217-%E6%B6%88%E6%81%AF%E7%9A%84-push-%E5%AE%9E%E7%8E%B0)
+ - [2.1.8 消息重复发送的避免](#218-%E6%B6%88%E6%81%AF%E9%87%8D%E5%A4%8D%E5%8F%91%E9%80%81%E7%9A%84%E9%81%BF%E5%85%8D)
+ - [2.1.9 广播消费与集群消费](#219-%E5%B9%BF%E6%92%AD%E6%B6%88%E8%B4%B9%E4%B8%8E%E9%9B%86%E7%BE%A4%E6%B6%88%E8%B4%B9)
+ - [2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?](#2110-rocketmq-%E4%B8%8D%E4%BD%BF%E7%94%A8-zookeeper-%E4%BD%9C%E4%B8%BA%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E7%9A%84%E5%8E%9F%E5%9B%A0%E4%BB%A5%E5%8F%8A%E8%87%AA%E5%88%B6%E7%9A%84-nameserver-%E4%BC%98%E7%BC%BA%E7%82%B9)
+ - [2.1.11 其它](#2111-%E5%85%B6%E5%AE%83)
+- [3 参考](#3-%E5%8F%82%E8%80%83)
+
+
+
+# 1 单机版消息中心
+
+一个消息中心,最基本的需要支持多生产者、多消费者,例如下:
+
+```java
+class Scratch {
+
+ public static void main(String[] args) {
+ // 实际中会有 nameserver 服务来找到 broker 具体位置以及 broker 主从信息
+ Broker broker = new Broker();
+ Producer producer1 = new Producer();
+ producer1.connectBroker(broker);
+ Producer producer2 = new Producer();
+ producer2.connectBroker(broker);
+
+ Consumer consumer1 = new Consumer();
+ consumer1.connectBroker(broker);
+ Consumer consumer2 = new Consumer();
+ consumer2.connectBroker(broker);
+
+ for (int i = 0; i < 2; i++) {
+ producer1.asyncSendMsg("producer1 send msg" + i);
+ producer2.asyncSendMsg("producer2 send msg" + i);
+ }
+ System.out.println("broker has msg:" + broker.getAllMagByDisk());
+
+ for (int i = 0; i < 1; i++) {
+ System.out.println("consumer1 consume msg:" + consumer1.syncPullMsg());
+ }
+ for (int i = 0; i < 3; i++) {
+ System.out.println("consumer2 consume msg:" + consumer2.syncPullMsg());
+ }
+ }
+
+}
+
+class Producer {
+
+ private Broker broker;
+
+ public void connectBroker(Broker broker) {
+ this.broker = broker;
+ }
+
+ public void asyncSendMsg(String msg) {
+ if (broker == null) {
+ throw new RuntimeException("please connect broker first");
+ }
+ new Thread(() -> {
+ broker.sendMsg(msg);
+ }).start();
+ }
+}
+
+class Consumer {
+ private Broker broker;
+
+ public void connectBroker(Broker broker) {
+ this.broker = broker;
+ }
+
+ public String syncPullMsg() {
+ return broker.getMsg();
+ }
+
+}
+
+class Broker {
+
+ // 对应 RocketMQ 中 MessageQueue,默认情况下 1 个 Topic 包含 4 个 MessageQueue
+ private LinkedBlockingQueue messageQueue = new LinkedBlockingQueue(Integer.MAX_VALUE);
+
+ // 实际发送消息到 broker 服务器使用 Netty 发送
+ public void sendMsg(String msg) {
+ try {
+ messageQueue.put(msg);
+ // 实际会同步或异步落盘,异步落盘使用的定时任务定时扫描落盘
+ } catch (InterruptedException e) {
+
+ }
+ }
+
+ public String getMsg() {
+ try {
+ return messageQueue.take();
+ } catch (InterruptedException e) {
+
+ }
+ return null;
+ }
+
+ public String getAllMagByDisk() {
+ StringBuilder sb = new StringBuilder("\n");
+ messageQueue.iterator().forEachRemaining((msg) -> {
+ sb.append(msg + "\n");
+ });
+ return sb.toString();
+ }
+}
+```
+
+问题:
+1. 没有实现真正执行消息存储落盘
+2. 没有实现 NameServer 去作为注册中心,定位服务
+3. 使用 LinkedBlockingQueue 作为消息队列,注意,参数是无限大,在真正 RocketMQ 也是如此是无限大,理论上不会出现对进来的数据进行抛弃,但是会有内存泄漏问题(阿里巴巴开发手册也因为这个问题,建议我们使用自制线程池)
+4. 没有使用多个队列(即多个 LinkedBlockingQueue),RocketMQ 的顺序消息是通过生产者和消费者同时使用同一个 MessageQueue 来实现,但是如果我们只有一个 MessageQueue,那我们天然就支持顺序消息
+5. 没有使用 MappedByteBuffer 来实现文件映射从而使消息数据落盘非常的快(实际 RocketMQ 使用的是 FileChannel+DirectBuffer)
+
+# 2 分布式消息中心
+
+## 2.1 问题与解决
+
+### 2.1.1 消息丢失的问题
+
+1. 当你系统需要保证百分百消息不丢失,你可以使用生产者每发送一个消息,Broker 同步返回一个消息发送成功的反馈消息
+2. 即每发送一个消息,同步落盘后才返回生产者消息发送成功,这样只要生产者得到了消息发送生成的返回,事后除了硬盘损坏,都可以保证不会消息丢失
+3. 但是这同时引入了一个问题,同步落盘怎么才能快?
+
+### 2.1.2 同步落盘怎么才能快
+
+1. 使用 FileChannel + DirectBuffer 池,使用堆外内存,加快内存拷贝
+2. 使用数据和索引分离,当消息需要写入时,使用 commitlog 文件顺序写,当需要定位某个消息时,查询index 文件来定位,从而减少文件IO随机读写的性能损耗
+
+### 2.1.3 消息堆积的问题
+
+1. 后台定时任务每隔72小时,删除旧的没有使用过的消息信息
+2. 根据不同的业务实现不同的丢弃任务,具体参考线程池的 AbortPolicy,例如FIFO/LRU等(RocketMQ没有此策略)
+3. 消息定时转移,或者对某些重要的 TAG 型(支付型)消息真正落库
+
+### 2.1.4 定时消息的实现
+
+1. 实际 RocketMQ 没有实现任意精度的定时消息,它只支持某些特定的时间精度的定时消息
+2. 实现定时消息的原理是:创建特定时间精度的 MessageQueue,例如生产者需要定时1s之后被消费者消费,你只需要将此消息发送到特定的 Topic,例如:MessageQueue-1 表示这个 MessageQueue 里面的消息都会延迟一秒被消费,然后 Broker 会在 1s 后发送到消费者消费此消息,使用 newSingleThreadScheduledExecutor 实现
+
+### 2.1.5 顺序消息的实现
+
+1. 与定时消息同原理,生产者生产消息时指定特定的 MessageQueue ,消费者消费消息时,消费特定的 MessageQueue,其实单机版的消息中心在一个 MessageQueue 就天然支持了顺序消息
+2. 注意:同一个 MessageQueue 保证里面的消息是顺序消费的前提是:消费者是串行的消费该 MessageQueue,因为就算 MessageQueue 是顺序的,但是当并行消费时,还是会有顺序问题,但是串行消费也同时引入了两个问题:
+>1. 引入锁来实现串行
+>2. 前一个消费阻塞时后面都会被阻塞
+
+### 2.1.6 分布式消息的实现
+
+1. 需要前置知识:2PC
+2. RocketMQ4.3 起支持,原理为2PC,即两阶段提交,prepared->commit/rollback
+3. 生产者发送事务消息,假设该事务消息 Topic 为 Topic1-Trans,Broker 得到后首先更改该消息的 Topic 为 Topic1-Prepared,该 Topic1-Prepared 对消费者不可见。然后定时回调生产者的本地事务A执行状态,根据本地事务A执行状态,来是否将该消息修改为 Topic1-Commit 或 Topic1-Rollback,消费者就可以正常找到该事务消息或者不执行等
+
+>注意,就算是事务消息最后回滚了也不会物理删除,只会逻辑删除该消息
+
+### 2.1.7 消息的 push 实现
+
+1. 注意,RocketMQ 已经说了自己会有低延迟问题,其中就包括这个消息的 push 延迟问题
+2. 因为这并不是真正的将消息主动的推送到消费者,而是 Broker 定时任务每5s将消息推送到消费者
+
+### 2.1.8 消息重复发送的避免
+
+1. RocketMQ 会出现消息重复发送的问题,因为在网络延迟的情况下,这种问题不可避免的发生,如果非要实现消息不可重复发送,那基本太难,因为网络环境无法预知,还会使程序复杂度加大,因此默认允许消息重复发送
+2. RocketMQ 让使用者在消费者端去解决该问题,即需要消费者端在消费消息时支持幂等性的去消费消息
+3. 最简单的解决方案是每条消费记录有个消费状态字段,根据这个消费状态字段来是否消费或者使用一个集中式的表,来存储所有消息的消费状态,从而避免重复消费
+4. 具体实现可以查询关于消息幂等消费的解决方案
+
+### 2.1.9 广播消费与集群消费
+
+1. 消息消费区别:广播消费,订阅该 Topic 的消息者们都会消费**每个**消息。集群消费,订阅该 Topic 的消息者们只会有一个去消费**某个**消息
+2. 消息落盘区别:具体表现在消息消费进度的保存上。广播消费,由于每个消费者都独立的去消费每个消息,因此每个消费者各自保存自己的消息消费进度。而集群消费下,订阅了某个 Topic,而旗下又有多个 MessageQueue,每个消费者都可能会去消费不同的 MessageQueue,因此总体的消费进度保存在 Broker 上集中的管理
+
+### 2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?
+
+1. ZooKeeper 作为支持顺序一致性的中间件,在某些情况下,它为了满足一致性,会丢失一定时间内的可用性,RocketMQ 需要注册中心只是为了发现组件地址,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,这同时也是 NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致
+2. 另外,当有新的服务器加入时,NameServer 并不会立马通知到 Produer,而是由 Produer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息(这种情况是通过 Producer 发送消息时,负载均衡解决)
+
+### 2.1.11 其它
+
+![][1]
+
+加分项咯
+1. 包括组件通信间使用 Netty 的自定义协议
+2. 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略)
+3. 消息过滤器(Producer 发送消息到 Broker,Broker 存储消息信息,Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤)
+4. Broker 同步双写和异步双写中 Master 和 Slave 的交互
+5. Broker 在 4.5.0 版本更新中引入了基于 Raft 协议的多副本选举,之前这是商业版才有的特性 [ISSUE-1046][2]
+
+# 3 参考
+
+1. 《RocketMQ技术内幕》:https://blog.csdn.net/prestigeding/article/details/85233529
+2. 关于 RocketMQ 对 MappedByteBuffer 的一点优化:https://lishoubo.github.io/2017/09/27/MappedByteBuffer%E7%9A%84%E4%B8%80%E7%82%B9%E4%BC%98%E5%8C%96/
+3. 阿里中间件团队博客-十分钟入门RocketMQ:http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/
+4. 分布式事务的种类以及 RocketMQ 支持的分布式消息:https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
+5. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践:https://yq.aliyun.com/articles/664608
+6. 基于《RocketMQ技术内幕》源码注释:https://github.com/LiWenGu/awesome-rocketmq
+
+[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/RocketMQ%E6%B5%81%E7%A8%8B.png
+[2]: http://rocketmq.apache.org/release_notes/release-notes-4.5.0/
diff --git a/docs/system-design/data-communication/RocketMQ.md b/docs/system-design/data-communication/RocketMQ.md
new file mode 100644
index 00000000..c74fab6f
--- /dev/null
+++ b/docs/system-design/data-communication/RocketMQ.md
@@ -0,0 +1,454 @@
+> 文章很长,点赞再看,养成好习惯😋😋😋
+>
+> [本文由 FrancisQ 老哥投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
+
+## 消息队列扫盲
+
+消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道似啥吧?
+
+所以问题并不是消息队列是什么,而是 **消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带来什么好处?消息队列会带来副作用吗?**
+
+### 消息队列为什么会出现?
+
+消息队列算是作为后端程序员的一个必备技能吧,因为**分布式应用必定涉及到各个系统之间的通信问题**,这个时候消息队列也应运而生了。可以说分布式的产生是消息队列的基础,而分布式怕是一个很古老的概念了吧,所以消息队列也是一个很古老的中间件了。
+
+### 消息队列能用来干什么?
+
+#### 异步
+
+你可能会反驳我,应用之间的通信又不是只能由消息队列解决,好好的通信为什么中间非要插一个消息队列呢?我不能直接进行通信吗?
+
+很好👍,你又提出了一个概念,**同步通信**。就比如现在业界使用比较多的 `Dubbo` 就是一个适用于各个系统之间同步通信的 `RPC` 框架。
+
+我来举个🌰吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。
+
+
+
+我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。
+
+当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短息系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢?
+
+
+
+这样整个系统的调用链又变长了,整个时间就变成了550ms。
+
+当我们在学生时代需要在食堂排队的时候,我们和食堂大妈就是一个同步的模型。
+
+我们需要告诉食堂大妈:“姐姐,给我加个鸡腿,再加个酸辣土豆丝,帮我浇点汁上去,多打点饭哦😋😋😋” 咦~~~ 为了多吃点,真恶心。
+
+然后大妈帮我们打饭配菜,我们看着大妈那颤抖的手和掉落的土豆丝不禁咽了咽口水。
+
+最终我们从大妈手中接过饭菜然后去寻找座位了...
+
+回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。
+
+
+
+那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以看其他事情了,这是一个 **异步** 的概念。
+
+所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。
+
+
+
+这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。
+
+> 但是你需要注意的是,整个流程的时长是没变的,就像你仅仅告诉服务员要吃什么是不会影响到做面的速度的。
+
+#### 解耦
+
+回到最初同步调用的过程,我们写个伪代码简单概括一下。
+
+
+
+那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码?
+
+
+
+如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用?
+
+
+
+这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。
+
+我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。
+
+
+
+> 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。
+
+#### 削峰
+
+我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样?
+
+
+
+如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了?
+
+短信业务又不是我们的主业务,我们能不能 **折中处理** 呢?如果我们把购买完成的信息发送到消息队列中,而短信系统 **尽自己所能地去消息队列中取消息和消费消息** ,即使处理速度慢一点也无所谓,只要我们的系统没有崩溃就行了。
+
+留得江山在,还怕没柴烧?你敢说每次发送验证码的时候是一发你就收到了的么?
+
+#### 消息队列能带来什么好处?
+
+其实上面我已经说了。**异步、解耦、削峰。** 哪怕你上面的都没看懂也千万要记住这六个字,因为他不仅是消息队列的精华,更是编程和架构的精华。
+
+#### 消息队列会带来副作用吗?
+
+没有哪一门技术是“银弹”,消息队列也有它的副作用。
+
+比如,本来好好的两个系统之间的调用,我中间加了个消息队列,如果消息队列挂了怎么办呢?是不是 **降低了系统的可用性** ?
+
+那这样是不是要保证HA(高可用)?是不是要搞集群?那么我 **整个系统的复杂度是不是上升了** ?
+
+抛开上面的问题不讲,万一我发送方发送失败了,然后执行重试,这样就可能产生重复的消息。
+
+或者我消费端处理失败了,请求重发,这样也会产生重复的消息。
+
+对于一些微服务来说,消费重复消息会带来更大的麻烦,比如增加积分,这个时候我加了多次是不是对其他用户不公平?
+
+那么,又 **如何解决重复消费消息的问题** 呢?
+
+如果我们此时的消息需要保证严格的顺序性怎么办呢?比如生产者生产了一系列的有序消息(对一个id为1的记录进行删除增加修改),但是我们知道在发布订阅模型中,对于主题是无顺序的,那么这个时候就会导致对于消费者消费消息的时候没有按照生产者的发送顺序消费,比如这个时候我们消费的顺序为修改删除增加,如果该记录涉及到金额的话是不是会出大事情?
+
+那么,又 **如何解决消息的顺序消费问题** 呢?
+
+就拿我们上面所讲的分布式系统来说,用户购票完成之后是不是需要增加账户积分?在同一个系统中我们一般会使用事务来进行解决,如果用 `Spring` 的话我们在上面伪代码中加入 `@Transactional` 注解就好了。但是在不同系统中如何保证事务呢?总不能这个系统我扣钱成功了你那积分系统积分没加吧?或者说我这扣钱明明失败了,你那积分系统给我加了积分。
+
+那么,又如何 **解决分布式事务问题** 呢?
+
+我们刚刚说了,消息队列可以进行削峰操作,那如果我的消费者如果消费很慢或者生产者生产消息很快,这样是不是会将消息堆积在消息队列中?
+
+那么,又如何 **解决消息堆积的问题** 呢?
+
+可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊😵?
+
+
+
+别急,办法总是有的。
+
+## RocketMQ是什么?
+
+
+
+哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬
+
+别急别急,话说你现在清楚 `MQ` 的构造吗,我还没讲呢,我们先搞明白 `MQ` 的内部构造,再来看看如何解决上面的一系列问题吧,不过你最好带着问题去阅读和了解喔。
+
+`RocketMQ` 是一个 **队列模型** 的消息中间件,具有**高性能、高可靠、高实时、分布式** 的特点。它是一个采用 `Java` 语言开发的分布式的消息系统,由阿里巴巴团队开发,在2016年底贡献给 `Apache`,成为了 `Apache` 的一个顶级项目。 在阿里内部,`RocketMQ` 很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 `RocketMQ` 流转。
+
+废话不多说,想要了解 `RocketMQ` 历史的同学可以自己去搜寻资料。听完上面的介绍,你只要知道 `RocketMQ` 很快、很牛、而且经历过双十一的实践就行了!
+
+## 队列模型和主题模型
+
+在谈 `RocketMQ` 的技术架构之前,我们先来了解一下两个名词概念——**队列模型** 和 **主题模型** 。
+
+首先我问一个问题,消息队列为什么要叫消息队列?
+
+你可能觉得很弱智,这玩意不就是存放消息的队列嘛?不叫消息队列叫什么?
+
+的确,早期的消息中间件是通过 **队列** 这一模型来实现的,可能是历史原因,我们都习惯把消息中间件成为消息队列。
+
+但是,如今例如 `RocketMQ` 、`Kafka` 这些优秀的消息中间件不仅仅是通过一个 **队列** 来实现消息存储的。
+
+### 队列模型
+
+就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。
+
+
+
+在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。
+
+当然你可以让 `Producer` 生产消息放入多个队列中,然后每个队列去对应每一个消费者。问题是可以解决,创建多个队列并且复制多份消息是会很影响资源和性能的。而且,这样子就会导致生产者需要知道具体消费者个数然后去复制对应数量的消息队列,这就违背我们消息中间件的 **解耦** 这一原则。
+
+### 主题模型
+
+那么有没有好的方法去解决这一个问题呢?有,那就是 **主题模型** 或者可以称为 **发布订阅模型** 。
+
+> 感兴趣的同学可以去了解一下设计模式里面的观察者模式并且手动实现一下,我相信你会有所收获的。
+
+在主题模型中,消息的生产者称为 **发布者(Publisher)** ,消息的消费者称为 **订阅者(Subscriber)** ,存放消息的容器称为 **主题(Topic)** 。
+
+其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。
+
+
+
+### RocketMQ中的消息模型
+
+`RockerMQ` 中的消息模型就是按照 **主题模型** 所实现的。你可能会好奇这个 **主题** 到底是怎么实现的呢?你上面也没有讲到呀!
+
+其实对于主题模型的实现来说每个消息中间件的底层设计都是不一样的,就比如 `Kafka` 中的 **分区** ,`RocketMQ` 中的 **队列** ,`RabbitMQ` 中的 `Exchange` 。我们可以理解为 **主题模型/发布订阅模型** 就是一个标准,那些中间件只不过照着这个标准去实现而已。
+
+所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。
+
+
+
+我们可以看到在整个图中有 `Producer Group` 、`Topic` 、`Consumer Group` 三个角色,我来分别介绍一下他们。
+
+- `Producer Group` 生产者组: 代表某一类的生产者,比如我们有多个秒杀系统作为生产者,这多个合在一起就是一个 `Producer Group` 生产者组,它们一般生产相同的消息。
+- `Consumer Group` 消费者组: 代表某一类的消费者,比如我们有多个短信系统作为消费者,这多个合在一起就是一个 `Consumer Group` 消费者组,它们一般消费相同的消息。
+- `Topic` 主题: 代表一类消息,比如订单消息,物流消息等等。
+
+你可以看到图中生产者组中的生产者会向主题发送消息,而 **主题中存在多个队列**,生产者每次生产消息之后是指定主题中的某个队列发送消息的。
+
+每个主题中都有多个队列(这里还不涉及到 `Broker`),集群消费模式下,一个消费者集群多台机器共同消费一个 `topic` 的多个队列,**一个队列只会被一个消费者消费**。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。就像上图中 `Consumer1` 和 `Consumer2` 分别对应着两个队列,而 `Consuer3` 是没有队列对应的,所以一般来讲要控制 **消费者组中的消费者个数和主题中队列个数相同** 。
+
+当然也可以消费者个数小于队列个数,只不过不太建议。如下图。
+
+
+
+**每个消费组在每个队列上维护一个消费位置** ,为什么呢?
+
+因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。
+
+
+
+可能你还有一个问题,**为什么一个主题中需要维护多个队列** ?
+
+答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。
+
+
+
+但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。
+
+所以总结来说,`RocketMQ` 通过**使用在一个 `Topic` 中配置多个队列并且每个队列维护每个消费者组的消费位置** 实现了 **主题模式/发布订阅模式** 。
+
+## RocketMQ的架构图
+
+讲完了消息模型,我们理解起 `RocketMQ` 的技术架构起来就容易多了。
+
+`RocketMQ` 技术架构中有四大角色 `NameServer` 、`Broker` 、`Producer` 、`Consumer` 。我来向大家分别解释一下这四个角色是干啥的。
+
+- `Broker`: 主要负责消息的存储、投递和查询以及服务高可用保证。说白了就是消息队列服务器嘛,生产者生产消息到 `Broker` ,消费者从 `Broker` 拉取消息并消费。
+
+ 这里,我还得普及一下关于 `Broker` 、`Topic` 和 队列的关系。上面我讲解了 `Topic` 和队列的关系——一个 `Topic` 中存在多个队列,那么这个 `Topic` 和队列存放在哪呢?
+
+ **一个 `Topic` 分布在多个 `Broker`上,一个 `Broker` 可以配置多个 `Topic` ,它们是多对多的关系**。
+
+ 如果某个 `Topic` 消息量很大,应该给它多配置几个队列(上文中提到了提高并发能力),并且 **尽量多分布在不同 `Broker` 上,以减轻某个 `Broker` 的压力** 。
+
+ `Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。
+
+ 
+
+ > 所以说我们需要配置多个Broker。
+
+- `NameServer`: 不知道你们有没有接触过 `ZooKeeper` 和 `Spring Cloud` 中的 `Eureka` ,它其实也是一个 **注册中心** ,主要提供两个功能:**Broker管理** 和 **路由信息管理** 。说白了就是 `Broker` 会将自己的信息注册到 `NameServer` 中,此时 `NameServer` 就存放了很多 `Broker` 的信息(Broker的路由表),消费者和生产者就从 `NameServer` 中获取路由表然后照着路由表的信息和对应的 `Broker` 进行通信(生产者和消费者定期会向 `NameServer` 去查询相关的 `Broker` 的信息)。
+
+- `Producer`: 消息发布的角色,支持分布式集群方式部署。说白了就是生产者。
+
+- `Consumer`: 消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制。说白了就是消费者。
+
+听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么?
+
+
+
+嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer` 、`Consumer` 和 `Broker` 直接进行生产消息,消费消息不就好了么?
+
+但是,我们上文提到过 `Broker` 是需要保证高可用的,如果整个系统仅仅靠着一个 `Broker` 来维持的话,那么这个 `Broker` 的压力会不会很大?所以我们需要使用多个 `Broker` 来保证 **负载均衡** 。
+
+如果说,我们的消费者和生产者直接和多个 `Broker` 相连,那么当 `Broker` 修改的时候必定会牵连着每个生产者和消费者,这样就会产生耦合问题,而 `NameServer` 注册中心就是用来解决这个问题的。
+
+> 如果还不是很理解的话,可以去看我介绍 `Spring Cloud` 的那篇文章,其中介绍了 `Eureka` 注册中心。
+
+当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。
+
+
+
+其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来🤨。
+
+第一、我们的 `Broker` **做了集群并且还进行了主从部署** ,由于消息分布在各个 `Broker` 上,一旦某个 `Broker` 宕机,则该`Broker` 上的消息读写都会受到影响。所以 `Rocketmq` 提供了 `master/slave` 的结构,` salve` 定时从 `master` 同步数据(同步刷盘或者异步刷盘),如果 `master` 宕机,**则 `slave` 提供消费服务,但是不能写入消息** (后面我还会提到哦)。
+
+第二、为了保证 `HA` ,我们的 `NameServer` 也做了集群部署,但是请注意它是 **去中心化** 的。也就意味着它没有主节点,你可以很明显地看出 `NameServer` 的所有节点是没有进行 `Info Replicate` 的,在 `RocketMQ` 中是通过 **单个Broker和所有NameServer保持长连接** ,并且在每隔30秒 `Broker` 会向所有 `Nameserver` 发送心跳,心跳包含了自身的 `Topic` 配置信息,这个步骤就对应这上面的 `Routing Info` 。
+
+第三、在生产者需要向 `Broker` 发送消息的时候,**需要先从 `NameServer` 获取关于 `Broker` 的路由信息**,然后通过 **轮询** 的方法去向每个队列中生产数据以达到 **负载均衡** 的效果。
+
+第四、消费者通过 `NameServer` 获取所有 `Broker` 的路由信息后,向 `Broker` 发送 `Pull` 请求来获取消息数据。`Consumer` 可以以两种模式启动—— **广播(Broadcast)和集群(Cluster)**。广播模式下,一条消息会发送给 **同一个消费组中的所有消费者** ,集群模式下消息只会发送给一个消费者。
+
+## 如何解决 顺序消费、重复消费
+
+其实,这些东西都是我在介绍消息队列带来的一些副作用的时候提到的,也就是说,这些问题不仅仅挂钩于 `RocketMQ` ,而是应该每个消息中间件都需要去解决的。
+
+在上面我介绍 `RocketMQ` 的技术架构的时候我已经向你展示了 **它是如何保证高可用的** ,这里不涉及运维方面的搭建,如果你感兴趣可以自己去官网上照着例子搭建属于你自己的 `RocketMQ` 集群。
+
+> 其实 `Kafka` 的架构基本和 `RocketMQ` 类似,只是它注册中心使用了 `Zookeeper` 、它的 **分区** 就相当于 `RocketMQ` 中的 **队列** 。还有一些小细节不同会在后面提到。
+
+### 顺序消费
+
+在上面的技术架构介绍中,我们已经知道了 **`RocketMQ` 在主题上是无序的、它只有在队列层面才是保证有序** 的。
+
+这又扯到两个概念——**普通顺序** 和 **严格顺序** 。
+
+所谓普通顺序是指 消费者通过 **同一个消费队列收到的消息是有顺序的** ,不同消息队列收到的消息则可能是无顺序的。普通顺序消息在 `Broker` **重启情况下不会保证消息顺序性** (短暂时间) 。
+
+所谓严格顺序是指 消费者收到的 **所有消息** 均是有顺序的。严格顺序消息 **即使在异常情况下也会保证消息的顺序性** 。
+
+但是,严格顺序看起来虽好,实现它可会付出巨大的代价。如果你使用严格顺序模式,`Broker` 集群中只要有一台机器不可用,则整个集群都不可用。你还用啥?现在主要场景也就在 `binlog` 同步。
+
+一般而言,我们的 `MQ` 都是能容忍短暂的乱序,所以推荐使用普通顺序模式。
+
+那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。
+
+
+
+那么,怎么解决呢?
+
+其实很简单,我们需要处理的仅仅是将同一语义下的消息放入同一个队列(比如这里是同一个订单),那我们就可以使用 **Hash取模法** 来保证同一个订单在同一个队列中就行了。
+
+### 重复消费
+
+emmm,就两个字—— **幂等** 。在编程中一个*幂等* 操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。比如说,这个时候我们有一个订单的处理积分的系统,每当来一个消息的时候它就负责为创建这个订单的用户的积分加上相应的数值。可是有一次,消息队列发送给订单系统 FrancisQ 的订单信息,其要求是给 FrancisQ 的积分加上 500。但是积分系统在收到 FrancisQ 的订单信息处理完成之后返回给消息队列处理成功的信息的时候出现了网络波动(当然还有很多种情况,比如Broker意外重启等等),这条回应没有发送成功。
+
+那么,消息队列没收到积分系统的回应会不会尝试重发这个消息?问题就来了,我再发这个消息,万一它又给 FrancisQ 的账户加上 500 积分怎么办呢?
+
+所以我们需要给我们的消费者实现 **幂等** ,也就是对同一个消息的处理结果,执行多少次都不变。
+
+那么如何给业务实现幂等呢?这个还是需要结合具体的业务的。你可以使用 **写入 `Redis`** 来保证,因为 `Redis` 的 `key` 和 `value` 就是天然支持幂等的。当然还有使用 **数据库插入法** ,基于数据库的唯一键来保证重复数据不会被插入多条。
+
+不过最主要的还是需要 **根据特定场景使用特定的解决方案** ,你要知道你的消息消费是否是完全不可重复消费还是可以忍受重复消费的,然后再选择强校验和弱校验的方式。毕竟在 CS 领域还是很少有技术银弹的说法。
+
+而在整个互联网领域,幂等不仅仅适用于消息队列的重复消费问题,这些实现幂等的方法,也同样适用于,**在其他场景中来解决重复请求或者重复调用的问题** 。比如将HTTP服务设计成幂等的,**解决前端或者APP重复提交表单数据的问题** ,也可以将一个微服务设计成幂等的,解决 `RPC` 框架自动重试导致的 **重复调用问题** 。
+
+## 分布式事务
+
+如何解释分布式事务呢?事务大家都知道吧?**要么都执行要么都不执行** 。在同一个系统中我们可以轻松地实现事务,但是在分布式架构中,我们有很多服务是部署在不同系统之间的,而不同服务之间又需要进行调用。比如此时我下订单然后增加积分,如果保证不了分布式事务的话,就会出现A系统下了订单,但是B系统增加积分失败或者A系统没有下订单,B系统却增加了积分。前者对用户不友好,后者对运营商不利,这是我们都不愿意见到的。
+
+那么,如何去解决这个问题呢?
+
+如今比较常见的分布式事务实现有 2PC、TCC 和事务消息(half 半消息机制)。每一种实现都有其特定的使用场景,但是也有各自的问题,**都不是完美的解决方案**。
+
+在 `RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。
+
+
+
+在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的** 。
+
+> 那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后 **改变主题** 为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,**然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费**,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
+
+你可以试想一下,如果没有从第5步开始的 **事务反查机制** ,如果出现网路波动第4步没有发送成功,这样就会产生 MQ 不知道是不是需要给消费者消费的问题,他就像一个无头苍蝇一样。在 `RocketMQ` 中就是使用的上述的事务反查来解决的,而在 `Kafka` 中通常是直接抛出一个异常让用户来自行解决。
+
+你还需要注意的是,在 `MQ Server` 指向系统B的操作已经和系统A不相关了,也就是说在消息队列中的分布式事务是——**本地事务和存储消息到消息队列才是同一个事务**。这样也就产生了事务的**最终一致性**,因为整个过程是异步的,**每个系统只要保证它自己那一部分的事务就行了**。
+
+## 消息堆积问题
+
+在上面我们提到了消息队列一个很重要的功能——**削峰** 。那么如果这个峰值太大了导致消息堆积在队列中怎么办呢?
+
+其实这个问题可以将它广义化,因为产生消息堆积的根源其实就只有两个——生产者生产太快或者消费者消费太慢。
+
+我们可以从多个角度去思考解决这个问题,当流量到峰值的时候是因为生产者生产太快,我们可以使用一些 **限流降级** 的方法,当然你也可以增加多个消费者实例去水平扩展增加消费能力来匹配生产的激增。如果消费者消费过慢的话,我们可以先检查 **是否是消费者出现了大量的消费错误** ,或者打印一下日志查看是否是哪一个线程卡死,出现了锁资源不释放等等的问题。
+
+> 当然,最快速解决消息堆积问题的方法还是增加消费者实例,不过 **同时你还需要增加每个主题的队列数量** 。
+>
+> 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。
+
+
+
+## 回溯消费
+
+回溯消费是指 `Consumer` 已经消费成功的消息,由于业务上需求需要重新消费,在`RocketMQ` 中, `Broker` 在向`Consumer` 投递成功消息后,**消息仍然需要保留** 。并且重新消费一般是按照时间维度,例如由于 `Consumer` 系统故障,恢复后需要重新消费1小时前的数据,那么 `Broker` 要提供一种机制,可以按照时间维度来回退消费进度。`RocketMQ` 支持按照时间回溯消费,时间维度精确到毫秒。
+
+这是官方文档的解释,我直接照搬过来就当科普了😁😁😁。
+
+## RocketMQ 的刷盘机制
+
+上面我讲了那么多的 `RocketMQ` 的架构和设计原理,你有没有好奇
+
+在 `Topic` 中的 **队列是以什么样的形式存在的?**
+
+**队列中的消息又是如何进行存储持久化的呢?**
+
+我在上文中提到的 **同步刷盘** 和 **异步刷盘** 又是什么呢?它们会给持久化带来什么样的影响呢?
+
+下面我将给你们一一解释。
+
+### 同步刷盘和异步刷盘
+
+
+
+如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。
+
+而异步刷盘往往是开启一个线程去异步地执行刷盘操作。消息刷盘采用后台异步线程提交的方式进行, **降低了读写延迟** ,提高了 `MQ` 的性能和吞吐量,一般适用于如发验证码等对于消息保证要求不太高的业务场景。
+
+一般地,**异步刷盘只有在 `Broker` 意外宕机的时候会丢失部分数据**,你可以设置 `Broker` 的参数 `FlushDiskType` 来调整你的刷盘策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。
+
+### 同步复制和异步复制
+
+上面的同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的 `Borker` 主从模式下,主节点返回消息给客户端的时候是否需要同步从节点。
+
+- 同步复制: 也叫 “同步双写”,也就是说,**只有消息同步双写到主从结点上时才返回写入成功** 。
+- 异步复制: **消息写入主节点之后就直接返回写入成功** 。
+
+然而,很多事情是没有完美的方案的,就比如我们进行消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,所以需要程序员根据特定业务场景去选择适应的主从复制方案。
+
+那么,**异步复制会不会也像异步刷盘那样影响消息的可靠性呢?**
+
+答案是不会的,因为两者就是不同的概念,对于消息可靠性是通过不同的刷盘策略保证的,而像异步同步复制策略仅仅是影响到了 **可用性** 。为什么呢?其主要原因**是 `RocketMQ` 是不支持自动主从切换的,当主节点挂掉之后,生产者就不能再给这个主节点生产消息了**。
+
+比如这个时候采用异步复制的方式,在主节点还未发送完需要同步的消息的时候主节点挂掉了,这个时候从节点就少了一部分消息。但是此时生产者无法再给主节点生产消息了,**消费者可以自动切换到从节点进行消费**(仅仅是消费),所以在主节点挂掉的时间只会产生主从结点短暂的消息不一致的情况,降低了可用性,而当主节点重启之后,从节点那部分未来得及复制的消息还会继续复制。
+
+在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。
+
+
+
+但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点A负责的是订单A的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点A的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。
+
+而在 `RocketMQ` 中采用了 `Dledger` 解决这个问题。他要求在写入消息的时候,要求**至少消息复制到半数以上的节点之后**,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的。这里我就不展开说明了,读者可以自己去了解。
+
+> 也不是说 `Dledger` 是个完美的方案,至少在 `Dledger` 选举过程中是无法提供服务的,而且他必须要使用三个节点或以上,如果多数节点同时挂掉他也是无法保证可用性的,而且要求消息复制板书以上节点的效率和直接异步复制还是有一定的差距的。
+
+### 存储机制
+
+还记得上面我们一开始的三个问题吗?到这里第三个问题已经解决了。
+
+但是,在 `Topic` 中的 **队列是以什么样的形式存在的?队列中的消息又是如何进行存储持久化的呢?** 还未解决,其实这里涉及到了 `RocketMQ` 是如何设计它的存储结构了。我首先想大家介绍 `RocketMQ` 消息存储架构中的三大角色——`CommitLog` 、`ConsumeQueue` 和 `IndexFile` 。
+
+- `CommitLog`: **消息主体以及元数据的存储主体**,存储 `Producer` 端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是**顺序写入日志文件**,当文件满了,写入下一个文件。
+- `ConsumeQueue`: 消息消费队列,**引入的目的主要是提高消息消费的性能**(我们再前面也讲了),由于`RocketMQ` 是基于主题 `Topic` 的订阅模式,消息消费是针对主题进行的,如果要遍历 `commitlog` 文件中根据 `Topic` 检索消息是非常低效的。`Consumer` 即可根据 `ConsumeQueue` 来查找待消费的消息。其中,`ConsumeQueue`(逻辑消费队列)**作为消费消息的索引**,保存了指定 `Topic` 下的队列消息在 `CommitLog` 中的**起始物理偏移量 `offset` **,消息大小 `size` 和消息 `Tag` 的 `HashCode` 值。**`consumequeue` 文件可以看成是基于 `topic` 的 `commitlog` 索引文件**,故 `consumequeue` 文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样 `consumequeue` 文件采取定长设计,每一个条目共20个字节,分别为8字节的 `commitlog` 物理偏移量、4字节的消息长度、8字节tag `hashcode`,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个 `ConsumeQueue`文件大小约5.72M;
+- `IndexFile`: `IndexFile`(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。这里只做科普不做详细介绍。
+
+总结来说,整个消息存储的结构,最主要的就是 `CommitLoq` 和 `ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。
+
+
+
+`RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。
+
+而 `RocketMQ` 为什么要这么做呢?原因是 **提高数据的写入效率** ,不分 `Topic` 意味着我们有更大的几率获取 **成批** 的消息进行数据写入,但也会带来一个麻烦就是读取消息的时候需要遍历整个大文件,这是非常耗时的。
+
+所以,在 `RocketMQ` 中又使用了 `ConsumeQueue` 作为每个队列的索引文件来 **提升读取消息的效率**。我们可以直接根据队列的消息序号,计算出索引的全局位置(索引序号*索引固定⻓度20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。
+
+讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。
+
+
+
+emmm,是不是有一点复杂🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。
+
+> 如果上面没看懂的读者一定要认真看下面的流程分析!
+
+首先,在最上面的那一块就是我刚刚讲的你现在可以直接 **把 `ConsumerQueue` 理解为 `Queue`**。
+
+在图中最左边说明了 红色方块 代表被写入的消息,虚线方块代表等待被写入的。左边的生产者发送消息会指定 `Topic` 、`QueueId` 和具体消息内容,而在 `Broker` 中管你是哪门子消息,他直接 **全部顺序存储到了 CommitLog **。而根据生产者指定的 `Topic` 和 `QueueId` 将这条消息本身在 `CommitLog` 的偏移(offset),消息本身大小,和tag的hash值存入对应的 `ConsumeQueue` 索引文件中。而在每个队列中都保存了 `ConsumeOffset` 即每个消费者组的消费位置(我在架构那里提到了,忘了的同学可以回去看一下),而消费者拉取消息进行消费的时候只需要根据 `ConsumeOffset` 获取下一个未被消费的消息就行了。
+
+上述就是我对于整个消息存储架构的大概理解(这里不涉及到一些细节讨论,比如稀疏索引等等问题),希望对你有帮助。
+
+因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考🤔🤔一下吧。
+
+
+
+为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。
+
+## 总结
+
+总算把这篇博客写完了。我讲的你们还记得吗😅?
+
+这篇文章中我主要想大家介绍了
+
+1. 消息队列出现的原因
+2. 消息队列的作用(异步,解耦,削峰)
+3. 消息队列带来的一系列问题(消息堆积、重复消费、顺序消费、分布式事务等等)
+4. 消息队列的两种消息模型——队列和主题模式
+5. 分析了 `RocketMQ` 的技术架构(`NameServer` 、`Broker` 、`Producer` 、`Comsumer`)
+6. 结合 `RocketMQ` 回答了消息队列副作用的解决方案
+7. 介绍了 `RocketMQ` 的存储机制和刷盘策略。
+
+等等。。。
+
+> 如果喜欢可以点赞哟👍👍👍。
\ No newline at end of file
diff --git a/docs/system-design/data-communication/dubbo.md b/docs/system-design/data-communication/dubbo.md
index 5cc6dc1b..f2e0ac48 100644
--- a/docs/system-design/data-communication/dubbo.md
+++ b/docs/system-design/data-communication/dubbo.md
@@ -44,7 +44,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
**什么是 RPC?**
-RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
+RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
**RPC原理是什么?**
@@ -81,10 +81,10 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo:
-1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务
+1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务。
2. **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。
-4. **服务降级**——某个服务挂掉之后调用备用服务
+4. **服务降级**——某个服务挂掉之后调用备用服务。
另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。
@@ -98,7 +98,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
-另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
+另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
## 二 Dubbo 的架构
@@ -108,20 +108,20 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
**上述节点简单说明:**
-- **Provider:** 暴露服务的服务提供方
-- **Consumer:** 调用远程服务的服务消费方
-- **Registry:** 服务注册与发现的注册中心
-- **Monitor:** 统计服务的调用次数和调用时间的监控中心
+- **Provider:** 暴露服务的服务提供方
+- **Consumer:** 调用远程服务的服务消费方
+- **Registry:** 服务注册与发现的注册中心
+- **Monitor:** 统计服务的调用次数和调用时间的监控中心
- **Container:** 服务运行容器
**调用关系说明:**
1. 服务容器负责启动,加载,运行服务提供者。
-2. 服务提供者在启动时,向注册中心注册自己提供的服务。
-3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
-4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
-5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
+2. 服务提供者在启动时,向注册中心注册自己提供的服务。
+3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
+4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
+5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
+6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
**重要知识点总结:**
@@ -156,7 +156,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
- 第七层:**protocol层**,远程调用层,封装rpc调用
- 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步
- 第九层:**transport层**,网络传输层,抽象mina和netty为统一接口
-- 第十层:**serialize层**,数据序列化层。网络传输需要。
+- 第十层:**serialize层**,数据序列化层,网络传输需要
## 三 Dubbo 的负载均衡策略
@@ -165,7 +165,7 @@ Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面
**先来个官方的解释。**
-> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件
+> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。
**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。**
@@ -251,7 +251,7 @@ HelloService helloService;
zookeeper宕机与dubbo直连的情况在面试中可能会被经常问到,所以要引起重视。
-在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种提现。
+在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种体现。
**dubbo的健壮性表现:**
diff --git a/docs/system-design/data-communication/kafka-inverview.md b/docs/system-design/data-communication/kafka-inverview.md
new file mode 100644
index 00000000..3788106f
--- /dev/null
+++ b/docs/system-design/data-communication/kafka-inverview.md
@@ -0,0 +1,210 @@
+------
+
+
+
+## Kafka面试题总结
+
+### Kafka 是什么?主要应用场景有哪些?
+
+Kafka 是一个分布式流式处理平台。这到底是什么意思呢?
+
+流平台具有三个关键功能:
+
+1. **消息队列**:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
+2. **容错的持久方式存储记录消息流**: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险·。
+3. **流式处理平台:** 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。
+
+Kafka 主要有两大应用场景:
+
+1. **消息队列** :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
+2. **数据处理:** 构建实时的流数据处理程序来转换或处理数据流。
+
+### 和其他消息队列相比,Kafka的优势在哪里?
+
+我们现在经常提到 Kafka 的时候就已经默认它是一个非常优秀的消息队列了,我们也会经常拿它给 RocketMQ、RabbitMQ 对比。我觉得 Kafka 相比其他消息队列主要的优势如下:
+
+1. **极致的性能** :基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
+2. **生态系统兼容性无可匹敌** :Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。
+
+实际上在早期的时候 Kafka 并不是一个合格的消息队列,早期的 Kafka 在消息队列领域就像是一个衣衫褴褛的孩子一样,功能不完备并且有一些小问题比如丢失消息、不保证消息可靠性等等。当然,这也和 LinkedIn 最早开发 Kafka 用于处理海量的日志有很大关系,哈哈哈,人家本来最开始就不是为了作为消息队列滴,谁知道后面误打误撞在消息队列领域占据了一席之地。
+
+随着后续的发展,这些短板都被 Kafka 逐步修复完善。所以,**Kafka 作为消息队列不可靠这个说法已经过时!**
+
+### 队列模型了解吗?Kafka 的消息模型知道吗?
+
+> 题外话:早期的 JMS 和 AMQP 属于消息服务领域权威组织所做的相关的标准,我在 [JavaGuide](https://github.com/Snailclimb/JavaGuide)的 [《消息队列其实很简单》](https://github.com/Snailclimb/JavaGuide#%E6%95%B0%E6%8D%AE%E9%80%9A%E4%BF%A1%E4%B8%AD%E9%97%B4%E4%BB%B6)这篇文章中介绍过。但是,这些标准的进化跟不上消息队列的演进速度,这些标准实际上已经属于废弃状态。所以,可能存在的情况是:不同的消息队列都有自己的一套消息模型。
+
+#### 队列模型:早期的消息模型
+
+
+
+**使用队列(Queue)作为消息通信载体,满足生产者与消费者模式,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。** 比如:我们生产者发送 100 条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
+
+**队列模型存在的问题:**
+
+假如我们存在这样一种情况:我们需要将生产者产生的消息分发给多个消费者,并且每个消费者都能接收到完成的消息内容。
+
+这种情况,队列模型就不好解决了。很多比较杠精的人就说:我们可以为每个消费者创建一个单独的队列,让生产者发送多份。这是一种非常愚蠢的做法,浪费资源不说,还违背了使用消息队列的目的。
+
+#### 发布-订阅模型:Kafka 消息模型
+
+发布-订阅模型主要是为了解决队列模型存在的问题。
+
+
+
+发布订阅模型(Pub-Sub) 使用**主题(Topic)** 作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
+
+**在发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。所以说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。**
+
+**Kafka 采用的就是发布 - 订阅模型。**
+
+> **RocketMQ 的消息模型和 Kafka 基本是完全一样的。唯一的区别是 Kafka 中没有队列这个概念,与之对应的是 Partition(分区)。**
+
+### 什么是Producer、Consumer、Broker、Topic、Partition?
+
+Kafka 将生产者发布的消息发送到 **Topic(主题)** 中,需要这些消息的消费者可以订阅这些 **Topic(主题)**,如下图所示:
+
+
+
+上面这张图也为我们引出了,Kafka 比较重要的几个概念:
+
+1. **Producer(生产者)** : 产生消息的一方。
+2. **Consumer(消费者)** : 消费消息的一方。
+3. **Broker(代理)** : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
+
+同时,你一定也注意到每个 Broker 中又包含了 Topic 以及 Partition 这两个重要的概念:
+
+- **Topic(主题)** : Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
+- **Partition(分区)** : Partition 属于 Topic 的一部分。一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,这也就表明一个 Topic 可以横跨多个 Broker 。这正如我上面所画的图一样。
+
+> 划重点:**Kafka 中的 Partition(分区) 实际上可以对应成为消息队列中的队列。这样是不是更好理解一点?**
+
+### Kafka 的多副本机制了解吗?带来了什么好处?
+
+还有一点我觉得比较重要的是 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。
+
+> 生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。
+
+**Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢?**
+
+1. Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
+2. Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。
+
+### Zookeeper 在 Kafka 中的作用知道吗?
+
+> **要想搞懂 zookeeper 在 Kafka 中的作用 一定要自己搭建一个 Kafka 环境然后自己进 zookeeper 去看一下有哪些文件夹和 Kafka 有关,每个节点又保存了什么信息。** 一定不要光看不实践,这样学来的也终会忘记!这部分内容参考和借鉴了这篇文章:https://www.jianshu.com/p/a036405f989c 。
+
+
+
+下图就是我的本地 Zookeeper ,它成功和我本地的 Kafka 关联上(以下文件夹结构借助 idea 插件 Zookeeper tool 实现)。
+
+
+
+ZooKeeper 主要为 Kafka 提供元数据的管理的功能。
+
+从图中我们可以看出,Zookeeper 主要为 Kafka 做了下面这些事情:
+
+1. **Broker 注册** :在 Zookeeper 上会有一个专门**用来进行 Broker 服务器列表记录**的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到/brokers/ids 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去
+2. **Topic 注册** : 在 Kafka 中,同一个**Topic 的消息会被分成多个分区**并将其分布在多个 Broker 上,**这些分区信息及与 Broker 的对应关系**也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:`/brokers/topics/my-topic/Partitions/0`、`/brokers/topics/my-topic/Partitions/1`
+3. **负载均衡** :上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
+4. ......
+
+### Kafka 如何保证消息的消费顺序?
+
+我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序,比如我们同时发了 2 个消息,这 2 个消息对应的操作分别对应的数据库操作是:更改用户会员等级、根据会员等级计算订单价格。假如这两条消息的消费顺序不一样造成的最终结果就会截然不同。
+
+我们知道 Kafka 中 Partition(分区)是真正保存消息的地方,我们发送的消息都被放在了这里。而我们的 Partition(分区) 又存在于 Topic(主题) 这个概念中,并且我们可以给特定 Topic 指定多个 Partition。
+
+
+
+每次添加消息到 Partition(分区) 的时候都会采用尾加法,如上图所示。Kafka 只能为我们保证 Partition(分区) 中的消息有序,而不能保证 Topic(主题) 中的 Partition(分区) 的有序。
+
+> 消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。Kafka 通过偏移量(offset)来保证消息在分区内的顺序性。
+
+所以,我们就有一种很简单的保证消息消费顺序的方法:**1 个 Topic 只对应一个 Partition**。这样当然可以解决问题,但是破坏了 Kafka 的设计初衷。
+
+Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。
+
+总结一下,对于如何保证 Kafka 中消息消费的顺序,有了下面两种方法:
+
+1. 1 个 Topic 只对应一个 Partition。
+2. (推荐)发送消息的时候指定 key/Partition。
+
+当然不仅仅只有上面两种方法,上面两种方法是我觉得比较好理解的,
+
+### Kafka 如何保证消息不丢失
+
+#### 生产者丢失消息的情况
+
+生产者(Producer) 调用`send`方法发送消息之后,消息可能因为网络问题并没有发送过去。
+
+所以,我们不能默认在调用`send`方法发送消息之后消息消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 `send` 方法发送消息实际上是异步的操作,我们可以通过 `get()`方法获取调用结果,但是这样也让它变为了同步操作,示例代码如下:
+
+> **详细代码见我的这篇文章:[Kafka系列第三篇!10 分钟学会如何在 Spring Boot 程序中使用 Kafka 作为消息队列?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486269&idx=2&sn=ec00417ad641dd8c3d145d74cafa09ce&chksm=cea244f6f9d5cde0c8eb233fcc4cf82e11acd06446719a7af55230649863a3ddd95f78d111de&token=1633957262&lang=zh_CN#rd)**
+
+```java
+SendResult sendResult = kafkaTemplate.send(topic, o).get();
+if (sendResult.getRecordMetadata() != null) {
+ logger.info("生产者成功发送消息到" + sendResult.getProducerRecord().topic() + "-> " + sendRe
+ sult.getProducerRecord().value().toString());
+}
+```
+
+但是一般不推荐这么做!可以采用为其添加回调函数的形式,示例代码如下:
+
+````java
+ ListenableFuture> future = kafkaTemplate.send(topic, o);
+ future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
+ ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));
+````
+
+如果消息发送失败的话,我们检查失败的原因之后重新发送即可!
+
+**另外这里推荐为 Producer 的`retries `(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了**
+
+#### 消费者丢失消息的情况
+
+我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。
+
+
+
+当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
+
+**解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。** 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。
+
+#### Kafka 弄丢了消息
+
+ 我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
+
+**试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。**
+
+**设置 acks = all**
+
+解决办法就是我们设置 **acks = all**。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
+
+acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。当我们配置 **acks = all** 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。
+
+**设置 replication.factor >= 3**
+
+为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 **replication.factor >= 3**。这样就可以保证每个 分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。
+
+**设置 min.insync.replicas > 1**
+
+一般情况下我们还需要设置 **min.insync.replicas> 1** ,这样配置代表消息至少要被写入到 2 个副本才算是被成功发送。**min.insync.replicas** 的默认值为 1 ,在实际生产中应尽量避免默认值 1。
+
+但是,为了保证整个 Kafka 服务的高可用性,你需要确保 **replication.factor > min.insync.replicas** 。为什么呢?设想一下加入两者相等的话,只要是有一个副本挂掉,整个分区就无法正常工作了。这明显违反高可用性!一般推荐设置成 **replication.factor = min.insync.replicas + 1**。
+
+**设置 unclean.leader.election.enable = false**
+
+> **Kafka 0.11.0.0版本开始 unclean.leader.election.enable 参数的默认值由原来的true 改为false**
+
+我们最开始也说了我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 **unclean.leader.election.enable = false** 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
+
+### Kafka 如何保证消息不重复消费
+
+代办...
+
+### Reference
+
+- Kafka 官方文档: https://kafka.apache.org/documentation/
+- 极客时间—《Kafka核心技术与实战》第11节:无消息丢失配置怎么实现?
diff --git a/docs/system-design/data-communication/message-queue.md b/docs/system-design/data-communication/message-queue.md
index e90a129a..3f99c07c 100644
--- a/docs/system-design/data-communication/message-queue.md
+++ b/docs/system-design/data-communication/message-queue.md
@@ -39,21 +39,22 @@
### (1) 通过异步处理提高系统性能(削峰、减少响应所需时间)
-
+
如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。**
通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
-
+
+
因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
### (2) 降低系统耦合性
- 我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
+ 使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。还是直接上图吧:
- 我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
-
-
+
+
+ 生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合, 这显然也提高了系统的扩展性。
**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
@@ -75,7 +76,7 @@
#### 4.1.1 JMS 简介
- JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
+ JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
**ActiveMQ 就是基于 JMS 规范实现的。**
@@ -84,12 +85,16 @@
①点到点(P2P)模型

- 使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
+
+
+使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
② 发布/订阅(Pub/Sub)模型

- 发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
+
+
+发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
#### 4.1.3 JMS 五种不同的消息正文格式
@@ -104,7 +109,7 @@
### 4.2 AMQP
- AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。
+ AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。
**RabbitMQ 就是基于 AMQP 协议实现的。**
diff --git a/docs/system-design/data-communication/rabbitmq.md b/docs/system-design/data-communication/rabbitmq.md
index 825f7123..79f24fdc 100644
--- a/docs/system-design/data-communication/rabbitmq.md
+++ b/docs/system-design/data-communication/rabbitmq.md
@@ -123,7 +123,7 @@ direct 类型常用在处理有优先级的任务,根据任务的优先级把
- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
-- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“.”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
+- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。

@@ -177,7 +177,7 @@ erlang 官网下载:[http://www.erlang.org/downloads](http://www.erlang.org/do
```shell
[root@SnailClimb local]#yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel
-```
+```
**5 进入erlang 安装包解压文件对 erlang 进行安装环境的配置**
@@ -253,7 +253,7 @@ wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.
```
或者直接在官网下载
-https://www.rabbitmq.com/install-rpm.html[enter link description here](https://www.rabbitmq.com/install-rpm.html)
+[https://www.rabbitmq.com/install-rpm.html](https://www.rabbitmq.com/install-rpm.html)
**2. 安装rpm**
diff --git a/docs/system-design/data-communication/why-use-rpc.md b/docs/system-design/data-communication/why-use-rpc.md
new file mode 100644
index 00000000..f295e13b
--- /dev/null
+++ b/docs/system-design/data-communication/why-use-rpc.md
@@ -0,0 +1,76 @@
+## 什么是 RPC?RPC原理是什么?
+
+### **什么是 RPC?**
+
+RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
+
+### **RPC原理是什么?**
+
+我这里这是简单的提一下,详细内容可以查看下面这篇文章:
+
+http://www.importnew.com/22003.html
+
+
+
+1. 服务消费方(client)调用以本地调用方式调用服务;
+2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
+3. client stub找到服务地址,并将消息发送到服务端;
+4. server stub收到消息后进行解码;
+5. server stub根据解码结果调用本地的服务;
+6. 本地服务执行并将结果返回给server stub;
+7. server stub将返回结果打包成消息并发送至消费方;
+8. client stub接收到消息,并进行解码;
+9. 服务消费方得到最终结果。
+
+下面再贴一个网上的时序图:
+
+
+
+### RPC 解决了什么问题?
+
+从上面对 RPC 介绍的内容中,概括来讲RPC 主要解决了:**让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。**
+
+### 常见的 RPC 框架总结?
+
+- **RMI(JDK自带):** JDK自带的RPC,有很多局限性,不推荐使用。
+- **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前 Dubbo 已经成为 Spring Cloud Alibaba 中的官方组件。
+- **gRPC** :gRPC是可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。
+
+- **Hessian:** Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
+- **Thrift:** Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
+
+## 既有 HTTP ,为啥用 RPC 进行服务调用?
+
+###RPC 只是一种设计而已
+
+RPC 只是一种概念、一种设计,就是为了解决 **不同服务之间的调用问题**, 它一般会包含有 **传输协议** 和 **序列化协议** 这两个。
+
+但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。
+
+### HTTP 和 TCP
+
+**可能现在很多对计算机网络不太熟悉的朋友已经被搞蒙了,要想真正搞懂,还需要来简单复习一下计算机网络基础知识:**
+
+> 我们通常谈计算机网络的五层协议的体系结构是指:应用层、传输层、网络层、数据链路层、物理层。
+>
+> **应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**HTTP 属于应用层协议,它会基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过 URL 向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。HTTP协议建立在 TCP 协议之上。
+>
+> **运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。TCP是传输层协议,主要解决数据如何在网络中传输。相比于UDP,**TCP** 提供的是**面向连接**的,**可靠的**数据传输服务。
+
+### RPC框架功能更齐全
+
+成熟的 RPC框架还提供好了“服务自动注册与发现”、"智能负载均衡"、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择
+RPC 进行服务注册和发现的一方面原因吧!
+
+**相关阅读:**
+
+- http://www.ruanyifeng.com/blog/2016/08/http.html (HTTP 协议入门- 阮一峰)
+
+### 一个常见的错误观点
+
+很多文章中还会提到说 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开,但是这个观点已经被否认,下面截取自知乎中一个回答,原回答地址:https://www.zhihu.com/question/41609070/answer/191965937。
+
+>首先要否认一点 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开。HTTP 协议是支持连接池复用的,也就是建立一定数量的连接不断开,并不会频繁的创建和销毁连接。二一要说的是 HTTP 也可以使用 Protobuf 这种二进制编码协议对内容进行编码,因此二者最大的区别还是在传输协议上。
+
+
+
diff --git a/docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md b/docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md
deleted file mode 100644
index 7840d844..00000000
--- a/docs/system-design/data-communication/数据通信(RESTful、RPC、消息队列).md
+++ /dev/null
@@ -1,100 +0,0 @@
-> ## RPC
-
-**RPC(Remote Procedure Call)—远程过程调用** ,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发分布式程序就像开发本地程序一样简单。
-
-**RPC采用客户端(服务调用方)/服务器端(服务提供方)模式,** 都运行在自己的JVM中。客户端只需要引入要使用的接口,接口的实现和运行都在服务器端。RPC主要依赖的技术包括序列化、反序列化和数据传输协议,这是一种定义与实现相分离的设计。
-
-**目前Java使用比较多的RPC方案主要有RMI(JDK自带)、Hessian、Dubbo以及Thrift等。**
-
-**注意: RPC主要指内部服务之间的调用,RESTful也可以用于内部服务之间的调用,但其主要用途还在于外部系统提供服务,因此没有将其包含在本知识点内。**
-
-### 常见RPC框架:
-
-- **RMI(JDK自带):** JDK自带的RPC
-
- 详细内容可以参考:[从懵逼到恍然大悟之Java中RMI的使用](https://blog.csdn.net/lmy86263/article/details/72594760)
-
-- **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。
-
- 详细内容可以参考:
-
- - [ 高性能优秀的服务框架-dubbo介绍](https://blog.csdn.net/qq_34337272/article/details/79862899)
-
- - [Dubbo是什么?能做什么?](https://blog.csdn.net/houshaolin/article/details/76408399)
-
-
-- **Hessian:** Hessian是一个轻量级的remotingonhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
-
- 详细内容可以参考: [Hessian的使用以及理解](https://blog.csdn.net/sunwei_pyw/article/details/74002351)
-
-- **Thrift:** Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
-
-
- 详细内容可以参考: [【Java】分布式RPC通信框架Apache Thrift 使用总结](https://www.cnblogs.com/zeze/p/8628585.html)
-
-### 如何进行选择:
-
-- **是否允许代码侵入:** 即需要依赖相应的代码生成器生成代码,比如Thrift。
-- **是否需要长连接获取高性能:** 如果对于性能需求较高的haul,那么可以果断选择基于TCP的Thrift、Dubbo。
-- **是否需要跨越网段、跨越防火墙:** 这种情况一般选择基于HTTP协议的Hessian和Thrift的HTTP Transport。
-
-此外,Google推出的基于HTTP2.0的gRPC框架也开始得到应用,其序列化协议基于Protobuf,网络框架使用的是Netty4,但是其需要生成代码,可扩展性也比较差。
-
-> ## 消息中间件
-
-**消息中间件,也可以叫做中央消息队列或者是消息队列(区别于本地消息队列,本地消息队列指的是JVM内的队列实现)**,是一种独立的队列系统,消息中间件经常用来解决内部服务之间的 **异步调用问题** 。请求服务方把请求队列放到队列中即可返回,然后等待服务提供方去队列中获取请求进行处理,之后通过回调等机制把结果返回给请求服务方。
-
-异步调用只是消息中间件一个非常常见的应用场景。此外,常用的消息队列应用场景还偷如下几个:
-- **解耦 :** 一个业务的非核心流程需要依赖其他系统,但结果并不重要,有通知即可。
-- **最终一致性 :** 指的是两个系统的状态保持一致,可以有一定的延迟,只要最终达到一致性即可。经常用在解决分布式事务上。
-- **广播 :** 消息队列最基本的功能。生产者只负责生产消息,订阅者接收消息。
-- **错峰和流控**
-
-
-具体可以参考:
-
-[《消息队列深入解析》](https://blog.csdn.net/qq_34337272/article/details/80029918)
-
-当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提到的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。
-
-- **ActiveMQ:** ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的JMSProvider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。
-
- 具体可以参考:
-
- [《消息队列ActiveMQ的使用详解》](https://blog.csdn.net/qq_34337272/article/details/80031702)
-
-- **RabbitMQ:** RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗
- > AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
-
-
- 具体可以参考:
-
- [《消息队列之 RabbitMQ》](https://www.jianshu.com/p/79ca08116d57)
-
-- **RocketMQ:**
-
- 具体可以参考:
-
- [《RocketMQ 实战之快速入门》](https://www.jianshu.com/p/824066d70da8)
-
- [《十分钟入门RocketMQ》](http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/) (阿里中间件团队博客)
-
-
-- **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统(现在官方的描述是“一个分布式流平台”),Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。
-
- 具体可以参考:
-
- [《Kafka应用场景》](http://book.51cto.com/art/201801/565244.htm)
-
- [《初谈Kafka》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484106&idx=1&sn=aa1999895d009d91eb3692a3e6429d18&chksm=fd9854abcaefddbd1101ca5dc2c7c783d7171320d6300d9b2d8e68b7ef8abd2b02ea03e03600#rd)
-
-**推荐阅读:**
-
-[《Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和区别》](https://mp.weixin.qq.com/s?__biz=MzU5OTMyODAyNg==&mid=2247484721&idx=1&sn=11e4e29886e581dd328311d308ccc068&chksm=feb7d144c9c058529465b02a4e26a25ef76b60be8984ace9e4a0f5d3d98ca52e014ecb73b061&scene=21#wechat_redirect)
-
-
-
-
-
-
-
diff --git a/docs/system-design/framework/Spring学习与面试.md b/docs/system-design/framework/Spring学习与面试.md
deleted file mode 100644
index dc56d44a..00000000
--- a/docs/system-design/framework/Spring学习与面试.md
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
-# Spring相关教程/资料:
-
-> ## 官网相关
-
- [Spring官网](https://spring.io/)
-
-[Spring系列主要项目](https://spring.io/projects)
-
-从配置到安全性,Web应用到大数据 - 无论您的应用程序的基础架构需求如何,都有一个Spring Project来帮助您构建它。 从小处着手,根据需要使用 - Spring是通过设计模块化的。
-
- [Spring官网指南](https://spring.io/guides)
-
-无论您在构建什么,这些指南都旨在尽可能快地提高您的工作效率 - 使用Spring团队推荐的最新Spring项目发布和技术。
-
- [Spring官方文档翻译(1~6章)](https://blog.csdn.net/tangtong1/article/details/51326887)
-
-> ## 系统学习教程:
-
-### 文档:
-
- [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html)
-
- [Spring W3Cschool教程 ](https://www.w3cschool.cn/wkspring/f6pk1ic8.html)
-
-### 视频:
-
-[网易云课堂——58集精通java教程Spring框架开发](http://study.163.com/course/courseMain.htm?courseId=1004475015#/courseDetail?tab=1&35)
-
- [慕课网相关视频](https://www.imooc.com/)
-
-**黑马视频(非常推荐):**
-微信公众号:“**Java面试通关手册**”后台回复“**资源分享第一波**”免费领取。
-
-> ## 一些常用的东西
-
-[Spring Framework 4.3.17.RELEASE API](https://docs.spring.io/spring/docs/4.3.17.RELEASE/javadoc-api/)
-
-默认浏览器打开,当需要查某个类的作用的时候,可以在浏览器通过ctrl+f搜索。
-
-
-# 面试必备知识点
-
-
-> ## SpringAOP,IOC实现原理
-
-AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、自己实现怎么实现一个IOC容器?这些东西都是经常会被问到的。
-
-[自己动手实现的 Spring IOC 和 AOP - 上篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-上篇/)
-
-[自己动手实现的 Spring IOC 和 AOP - 下篇](http://www.coolblog.xyz/2018/01/18/自己动手实现的-Spring-IOC-和-AOP-下篇/)
-
-### AOP:
-
-AOP思想的实现一般都是基于 **代理模式** ,在JAVA中一般采用JDK动态代理模式,但是我们都知道,**JDK动态代理模式只能代理接口而不能代理类**。因此,Spring AOP 会这样子来进行切换,因为Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。
-
-- 如果目标对象的实现类实现了接口,Spring AOP 将会采用 JDK 动态代理来生成 AOP 代理类;
-- 如果目标对象的实现类没有实现接口,Spring AOP 将会采用 CGLIB 来生成 AOP 代理类——不过这个选择过程对开发者完全透明、开发者也无需关心。
-
-
-
-[※静态代理、JDK动态代理、CGLIB动态代理讲解](http://www.cnblogs.com/puyangsky/p/6218925.html)
-
-我们知道AOP思想的实现一般都是基于 **代理模式** ,所以在看下面的文章之前建议先了解一下静态代理以及JDK动态代理、CGLIB动态代理的实现方式。
-
-[Spring AOP 入门](https://juejin.im/post/5aa7818af265da23844040c6)
-
-带你入门的一篇文章。这篇文章主要介绍了AOP中的基本概念:5种类型的通知(Before,After,After-returning,After-throwing,Around);Spring中对AOP的支持:AOP思想的实现一般都是基于代理模式,在JAVA中一般采用JDK动态代理模式,Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理,
-
-[※Spring AOP 基于AspectJ注解如何实现AOP](https://juejin.im/post/5a55af9e518825734d14813f)
-
-
-**AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)**,可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易
-
-Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。
-
-
-[※探秘Spring AOP(慕课网视频,很不错)](https://www.imooc.com/learn/869)
-
-慕课网视频,讲解的很不错,详细且深入
-
-
-[spring源码剖析(六)AOP实现原理剖析](https://blog.csdn.net/fighterandknight/article/details/51209822)
-
-通过源码分析Spring AOP的原理
-
-### IOC:
-
-Spring IOC的初始化过程:
-
-
-[[Spring框架]Spring IOC的原理及详解。](https://www.cnblogs.com/wang-meng/p/5597490.html)
-
-[Spring IOC核心源码学习](https://yikun.github.io/2015/05/29/Spring-IOC核心源码学习/)
-
-比较简短,推荐阅读。
-
-[Spring IOC 容器源码分析](https://javadoop.com/post/spring-ioc)
-
-强烈推荐,内容详尽,而且便于阅读。
-
-> ## Spring事务管理
-
-[可能是最漂亮的Spring事务管理详解](https://juejin.im/post/5b00c52ef265da0b95276091)
-
-[Spring编程式和声明式事务实例讲解](https://juejin.im/post/5b010f27518825426539ba38)
-
-> ## 其他
-
-**Spring单例与线程安全:**
-
-[Spring框架中的单例模式(源码解读)](http://www.cnblogs.com/chengxuyuanzhilu/p/6404991.html)
-
-单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例。spring依赖注入时,使用了 多重判断加锁 的单例模式。
-
-> ## Spring源码阅读
-
-阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。
-
-### [Spring源码阅读](https://github.com/seaswalker/Spring)
- - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md)
-- [spring-aop](https://github.com/seaswalker/Spring/blob/master/note/spring-aop.md)
-- [spring-context](https://github.com/seaswalker/Spring/blob/master/note/spring-context.md)
-- [spring-task](https://github.com/seaswalker/Spring/blob/master/note/spring-task.md)
-- [spring-transaction](https://github.com/seaswalker/Spring/blob/master/note/spring-transaction.md)
-- [spring-mvc](https://github.com/seaswalker/Spring/blob/master/note/spring-mvc.md)
-- [guava-cache](https://github.com/seaswalker/Spring/blob/master/note/guava-cache.md)
diff --git a/docs/system-design/framework/ZooKeeper-plus.md b/docs/system-design/framework/ZooKeeper-plus.md
new file mode 100644
index 00000000..d68a4c47
--- /dev/null
+++ b/docs/system-design/framework/ZooKeeper-plus.md
@@ -0,0 +1,375 @@
+[FrancisQ](https://juejin.im/user/5c33853851882525ea106810) 投稿。
+
+# ZooKeeper
+
+## 好久不见
+
+离上一篇文章的发布也快一个月了,想想已经快一个月没写东西了,其中可能有期末考试、课程设计和驾照考试,但这都不是借口!
+
+一到冬天就懒的不行,望广大掘友督促我🙄🙄✍️✍️。
+
+> 文章很长,先赞后看,养成习惯。❤️ 🧡 💛 💚 💙 💜
+
+## 什么是ZooKeeper
+
+`ZooKeeper` 由 `Yahoo` 开发,后来捐赠给了 `Apache` ,现已成为 `Apache` 顶级项目。`ZooKeeper` 是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于 `Paxos` 算法的 `ZAB` 协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
+
+
+
+简单来说, `ZooKeeper` 是一个 **分布式协调服务框架** 。分布式?协调服务?这啥玩意?🤔🤔
+
+其实解释到分布式这个概念的时候,我发现有些同学并不是能把 **分布式和集群 **这两个概念很好的理解透。前段时间有同学和我探讨起分布式的东西,他说分布式不就是加机器吗?一台机器不够用再加一台抗压呗。当然加机器这种说法也无可厚非,你一个分布式系统必定涉及到多个机器,但是你别忘了,计算机学科中还有一个相似的概念—— `Cluster` ,集群不也是加机器吗?但是 集群 和 分布式 其实就是两个完全不同的概念。
+
+比如,我现在有一个秒杀服务,并发量太大单机系统承受不住,那我加几台服务器也 **一样** 提供秒杀服务,这个时候就是 **`Cluster` 集群** 。
+
+
+
+但是,我现在换一种方式,我将一个秒杀服务 **拆分成多个子服务** ,比如创建订单服务,增加积分服务,扣优惠券服务等等,**然后我将这些子服务都部署在不同的服务器上** ,这个时候就是 **`Distributed` 分布式** 。
+
+
+
+而我为什么反驳同学所说的分布式就是加机器呢?因为我认为加机器更加适用于构建集群,因为它真是只有加机器。而对于分布式来说,你首先需要将业务进行拆分,然后再加机器(不仅仅是加机器那么简单),同时你还要去解决分布式带来的一系列问题。
+
+
+
+比如各个分布式组件如何协调起来,如何减少各个系统之间的耦合度,分布式事务的处理,如何去配置整个分布式系统等等。`ZooKeeper` 主要就是解决这些问题的。
+
+## 一致性问题
+
+设计一个分布式系统必定会遇到一个问题—— **因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡** 。这就是著名的 `CAP` 定理。
+
+理解起来其实很简单,比如说把一个班级作为整个系统,而学生是系统中的一个个独立的子系统。这个时候班里的小红小明偷偷谈恋爱被班里的大嘴巴小花发现了,小花欣喜若狂告诉了周围的人,然后小红小明谈恋爱的消息在班级里传播起来了。当在消息的传播(散布)过程中,你抓到一个同学问他们的情况,如果回答你不知道,那么说明整个班级系统出现了数据不一致的问题(因为小花已经知道这个消息了)。而如果他直接不回答你,因为整个班级有消息在进行传播(为了保证一致性,需要所有人都知道才可提供服务),这个时候就出现了系统的可用性问题。
+
+
+
+而上述前者就是 `Eureka` 的处理方式,它保证了AP(可用性),后者就是我们今天所要将的 `ZooKeeper` 的处理方式,它保证了CP(数据一致性)。
+
+## 一致性协议和算法
+
+而为了解决数据一致性问题,在科学家和程序员的不断探索中,就出现了很多的一致性协议和算法。比如 2PC(两阶段提交),3PC(三阶段提交),Paxos算法等等。
+
+这时候请你思考一个问题,同学之间如果采用传纸条的方式去传播消息,那么就会出现一个问题——我咋知道我的小纸条有没有传到我想要传递的那个人手中呢?万一被哪个小家伙给劫持篡改了呢,对吧?
+
+
+
+这个时候就引申出一个概念—— **拜占庭将军问题** 。它意指 **在不可靠信道上试图通过消息传递的方式达到一致性是不可能的**, 所以所有的一致性算法的 **必要前提** 就是安全可靠的消息通道。
+
+而为什么要去解决数据一致性的问题?你想想,如果一个秒杀系统将服务拆分成了下订单和加积分服务,这两个服务部署在不同的机器上了,万一在消息的传播过程中积分系统宕机了,总不能你这边下了订单却没加积分吧?你总得保证两边的数据需要一致吧?
+
+### 2PC(两阶段提交)
+
+两阶段提交是一种保证分布式系统数据一致性的协议,现在很多数据库都是采用的两阶段提交协议来完成 **分布式事务** 的处理。
+
+在介绍2PC之前,我们先来想想分布式事务到底有什么问题呢?
+
+还拿秒杀系统的下订单和加积分两个系统来举例吧(我想你们可能都吐了🤮🤮🤮),我们此时下完订单会发个消息给积分系统告诉它下面该增加积分了。如果我们仅仅是发送一个消息也不收回复,那么我们的订单系统怎么能知道积分系统的收到消息的情况呢?如果我们增加一个收回复的过程,那么当积分系统收到消息后返回给订单系统一个 `Response` ,但在中间出现了网络波动,那个回复消息没有发送成功,订单系统是不是以为积分系统消息接收失败了?它是不是会回滚事务?但此时积分系统是成功收到消息的,它就会去处理消息然后给用户增加积分,这个时候就会出现积分加了但是订单没下成功。
+
+所以我们所需要解决的是在分布式系统中,整个调用链中,我们所有服务的数据处理要么都成功要么都失败,即所有服务的 **原子性问题** 。
+
+在两阶段提交中,主要涉及到两个角色,分别是协调者和参与者。
+
+第一阶段:当要执行一个分布式事务的时候,事务发起者首先向协调者发起事务请求,然后协调者会给所有参与者发送 `prepare` 请求(其中包括事务内容)告诉参与者你们需要执行事务了,如果能执行我发的事务内容那么就先执行但不提交,执行后请给我回复。然后参与者收到 `prepare` 消息后,他们会开始执行事务(但不提交),并将 `Undo` 和 `Redo` 信息记入事务日志中,之后参与者就向协调者反馈是否准备好了。
+
+第二阶段:第二阶段主要是协调者根据参与者反馈的情况来决定接下来是否可以进行事务的提交操作,即提交事务或者回滚事务。
+
+比如这个时候 **所有的参与者** 都返回了准备好了的消息,这个时候就进行事务的提交,协调者此时会给所有的参与者发送 **`Commit` 请求** ,当参与者收到 `Commit` 请求的时候会执行前面执行的事务的 **提交操作** ,提交完毕之后将给协调者发送提交成功的响应。
+
+而如果在第一阶段并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送 **回滚事务的 `rollback` 请求**,参与者收到之后将会 **回滚它在第一阶段所做的事务处理** ,然后再将处理情况返回给协调者,最终协调者收到响应后便给事务发起者返回处理失败的结果。
+
+
+
+个人觉得 2PC 实现得还是比较鸡肋的,因为事实上它只解决了各个事务的原子性问题,随之也带来了很多的问题。
+
+
+
+* **单点故障问题**,如果协调者挂了那么整个系统都处于不可用的状态了。
+* **阻塞问题**,即当协调者发送 `prepare` 请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。
+* **数据不一致问题**,比如当第二阶段,协调者只发送了一部分的 `commit` 请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
+
+### 3PC(三阶段提交)
+
+因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 **3PC(三阶段提交)** 。那么这三阶段又分别是什么呢?
+
+> 千万不要吧PC理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
+
+1. **CanCommit阶段**:协调者向所有参与者发送 `CanCommit` 请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。
+2. **PreCommit阶段**:协调者根据参与者返回的响应来决定是否可以进行下面的 `PreCommit` 操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送 `PreCommit` 预提交请求,**参与者收到预提交请求后,会进行事务的执行操作,并将 `Undo` 和 `Redo` 信息写入事务日志中** ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 **任何一个 NO** 的信息,或者 **在一定时间内** 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。
+3. **DoCommit阶段**:这个阶段其实和 `2PC` 的第二阶段差不多,如果协调者收到了所有参与者在 `PreCommit` 阶段的 YES 响应,那么协调者将会给所有参与者发送 `DoCommit` 请求,**参与者收到 `DoCommit` 请求后则会进行事务的提交工作**,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在 `PreCommit` 阶段 **收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应** ,那么就会进行中断请求的发送,参与者收到中断请求后则会 **通过上面记录的回滚日志** 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
+
+
+
+> 这里是 `3PC` 在成功的环境下的流程图,你可以看到 `3PC` 在很多地方进行了超时中断的处理,比如协调者在指定时间内为收到全部的确认消息则进行事务中断的处理,这样能 **减少同步阻塞的时间** 。还有需要注意的是,**`3PC` 在 `DoCommit` 阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交**。为什么这么做呢?是因为这个时候我们肯定**保证了在第一阶段所有的协调者全部返回了可以执行事务的响应**,这个时候我们有理由**相信其他系统都能进行事务的执行和提交**,所以**不管**协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
+
+总之,`3PC` 通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 `PreCommit` 阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
+
+所以,要解决一致性问题还需要靠 `Paxos` 算法⭐️ ⭐️ ⭐️ 。
+
+### `Paxos` 算法
+
+`Paxos` 算法是基于**消息传递且具有高度容错特性的一致性算法**,是目前公认的解决分布式一致性问题最有效的算法之一,**其解决的问题就是在分布式系统中如何就某个值(决议)达成一致** 。
+
+在 `Paxos` 中主要有三个角色,分别为 `Proposer提案者`、`Acceptor表决者`、`Learner学习者`。`Paxos` 算法和 `2PC` 一样,也有两个阶段,分别为 `Prepare` 和 `accept` 阶段。
+
+#### prepare 阶段
+
+* `Proposer提案者`:负责提出 `proposal`,每个提案者在提出提案时都会首先获取到一个 **具有全局唯一性的、递增的提案编号N**,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在**第一阶段是只将提案编号发送给所有的表决者**。
+* `Acceptor表决者`:每个表决者在 `accept` 某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个**编号最大的提案**,其编号假设为 `maxN`。每个表决者仅会 `accept` 编号大于自己本地 `maxN` 的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给 `Proposer` 。
+
+> 下面是 `prepare` 阶段的流程图,你可以对照着参考一下。
+
+
+
+#### accept 阶段
+
+当一个提案被 `Proposer` 提出后,如果 `Proposer` 收到了超过半数的 `Acceptor` 的批准(`Proposer` 本身同意),那么此时 `Proposer` 会给所有的 `Acceptor` 发送真正的提案(你可以理解为第一阶段为试探),这个时候 `Proposer` 就会发送提案的内容和提案编号。
+
+表决者收到提案请求后会再次比较本身已经批准过的最大提案编号和该提案编号,如果该提案编号 **大于等于** 已经批准过的最大提案编号,那么就 `accept` 该提案(此时执行提案内容但不提交),随后将情况返回给 `Proposer` 。如果不满足则不回应或者返回 NO 。
+
+
+
+当 `Proposer` 收到超过半数的 `accept` ,那么它这个时候会向所有的 `acceptor` 发送提案的提交请求。需要注意的是,因为上述仅仅是超过半数的 `acceptor` 批准执行了该提案内容,其他没有批准的并没有执行该提案内容,所以这个时候需要**向未批准的 `acceptor` 发送提案内容和提案编号并让它无条件执行和提交**,而对于前面已经批准过该提案的 `acceptor` 来说 **仅仅需要发送该提案的编号** ,让 `acceptor` 执行提交就行了。
+
+
+
+而如果 `Proposer` 如果没有收到超过半数的 `accept` 那么它将会将 **递增** 该 `Proposal` 的编号,然后 **重新进入 `Prepare` 阶段** 。
+
+> 对于 `Learner` 来说如何去学习 `Acceptor` 批准的提案内容,这有很多方式,读者可以自己去了解一下,这里不做过多解释。
+
+#### `paxos` 算法的死循环问题
+
+其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁🤬🤬。
+
+比如说,此时提案者 P1 提出一个方案 M1,完成了 `Prepare` 阶段的工作,这个时候 `acceptor` 则批准了 M1,但是此时提案者 P2 同时也提出了一个方案 M2,它也完成了 `Prepare` 阶段的工作。然后 P1 的方案已经不能在第二阶段被批准了(因为 `acceptor` 已经批准了比 M1 更大的 M2),所以 P1 自增方案变为 M3 重新进入 `Prepare` 阶段,然后 `acceptor` ,又批准了新的 M3 方案,它又不能批准 M2 了,这个时候 M2 又自增进入 `Prepare` 阶段。。。
+
+就这样无休无止的永远提案下去,这就是 `paxos` 算法的死循环问题。
+
+
+
+那么如何解决呢?很简单,人多了容易吵架,我现在 **就允许一个能提案** 就行了。
+
+## 引出 `ZAB`
+
+### `Zookeeper` 架构
+
+作为一个优秀高效且可靠的分布式协调框架,`ZooKeeper` 在解决分布式数据一致性问题时并没有直接使用 `Paxos` ,而是专门定制了一致性协议叫做 `ZAB(ZooKeeper Automic Broadcast)` 原子广播协议,该协议能够很好地支持 **崩溃恢复** 。
+
+
+
+### `ZAB` 中的三个角色
+
+和介绍 `Paxos` 一样,在介绍 `ZAB` 协议之前,我们首先来了解一下在 `ZAB` 中三个主要的角色,`Leader 领导者`、`Follower跟随者`、`Observer观察者` 。
+
+* `Leader` :集群中 **唯一的写请求处理者** ,能够发起投票(投票也是为了进行写请求)。
+* `Follower`:能够接收客户端的请求,如果是读请求则可以自己处理,**如果是写请求则要转发给 `Leader`** 。在选举过程中会参与投票,**有选举权和被选举权** 。
+* `Observer` :就是没有选举权和被选举权的 `Follower` 。
+
+在 `ZAB` 协议中对 `zkServer`(即上面我们说的三个角色的总称) 还有两种模式的定义,分别是 **消息广播** 和 **崩溃恢复** 。
+
+### 消息广播模式
+
+说白了就是 `ZAB` 协议是如何处理写请求的,上面我们不是说只有 `Leader` 能处理写请求嘛?那么我们的 `Follower` 和 `Observer` 是不是也需要 **同步更新数据** 呢?总不能数据只在 `Leader` 中更新了,其他角色都没有得到更新吧?
+
+不就是 **在整个集群中保持数据的一致性** 嘛?如果是你,你会怎么做呢?
+
+
+
+废话,第一步肯定需要 `Leader` 将写请求 **广播** 出去呀,让 `Leader` 问问 `Followers` 是否同意更新,如果超过半数以上的同意那么就进行 `Follower` 和 `Observer` 的更新(和 `Paxos` 一样)。当然这么说有点虚,画张图理解一下。
+
+
+
+嗯。。。看起来很简单,貌似懂了🤥🤥🤥。这两个 `Queue` 哪冒出来的?答案是 **`ZAB` 需要让 `Follower` 和 `Observer` 保证顺序性** 。何为顺序性,比如我现在有一个写请求A,此时 `Leader` 将请求A广播出去,因为只需要半数同意就行,所以可能这个时候有一个 `Follower` F1因为网络原因没有收到,而 `Leader` 又广播了一个请求B,因为网络原因,F1竟然先收到了请求B然后才收到了请求A,这个时候请求处理的顺序不同就会导致数据的不同,从而 **产生数据不一致问题** 。
+
+所以在 `Leader` 这端,它为每个其他的 `zkServer` 准备了一个 **队列** ,采用先进先出的方式发送消息。由于协议是 **通过 `TCP` **来进行网络通信的,保证了消息的发送顺序性,接受顺序性也得到了保证。
+
+除此之外,在 `ZAB` 中还定义了一个 **全局单调递增的事务ID `ZXID`** ,它是一个64位long型,其中高32位表示 `epoch` 年代,低32位表示事务id。`epoch` 是会根据 `Leader` 的变化而变化的,当一个 `Leader` 挂了,新的 `Leader` 上位的时候,年代(`epoch`)就变了。而低32位可以简单理解为递增的事务id。
+
+定义这个的原因也是为了顺序性,每个 `proposal` 在 `Leader` 中生成后需要 **通过其 `ZXID` 来进行排序** ,才能得到处理。
+
+### 崩溃恢复模式
+
+说到崩溃恢复我们首先要提到 `ZAB` 中的 `Leader` 选举算法,当系统出现崩溃影响最大应该是 `Leader` 的崩溃,因为我们只有一个 `Leader` ,所以当 `Leader` 出现问题的时候我们势必需要重新选举 `Leader` 。
+
+`Leader` 选举可以分为两个不同的阶段,第一个是我们提到的 `Leader` 宕机需要重新选举,第二则是当 `Zookeeper` 启动时需要进行系统的 `Leader` 初始化选举。下面我先来介绍一下 `ZAB` 是如何进行初始化选举的。
+
+假设我们集群中有3台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了 `server1` ,它会首先 **投票给自己** ,投票内容为服务器的 `myid` 和 `ZXID` ,因为初始化所以 `ZXID` 都为0,此时 `server1` 发出的投票为 (1,0)。但此时 `server1` 的投票仅为1,所以不能作为 `Leader` ,此时还在选举阶段所以整个集群处于 **`Looking` 状态**。
+
+接着 `server2` 启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(`server1`也会,只是它那时没有其他的服务器了),`server1` 在收到 `server2` 的投票信息后会将投票信息与自己的作比较。**首先它会比较 `ZXID` ,`ZXID` 大的优先为 `Leader`,如果相同则比较 `myid`,`myid` 大的优先作为 `Leader`**。所以此时`server1` 发现 `server2` 更适合做 `Leader`,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后`server2` 收到之后发现和自己的一样无需做更改,并且自己的 **投票已经超过半数** ,则 **确定 `server2` 为 `Leader`**,`server1` 也会将自己服务器设置为 `Following` 变为 `Follower`。整个服务器就从 `Looking` 变为了正常状态。
+
+当 `server3` 启动发现集群没有处于 `Looking` 状态时,它会直接以 `Follower` 的身份加入集群。
+
+还是前面三个 `server` 的例子,如果在整个集群运行的过程中 `server2` 挂了,那么整个集群会如何重新选举 `Leader` 呢?其实和初始化选举差不多。
+
+首先毫无疑问的是剩下的两个 `Follower` 会将自己的状态 **从 `Following` 变为 `Looking` 状态** ,然后每个 `server` 会向初始化投票一样首先给自己投票(这不过这里的 `zxid` 可能不是0了,这里为了方便随便取个数字)。
+
+假设 `server1` 给自己投票为(1,99),然后广播给其他 `server`,`server3` 首先也会给自己投票(3,95),然后也广播给其他 `server`。`server1` 和 `server3` 此时会收到彼此的投票信息,和一开始选举一样,他们也会比较自己的投票和收到的投票(`zxid` 大的优先,如果相同那么就 `myid` 大的优先)。这个时候 `server1` 收到了 `server3` 的投票发现没自己的合适故不变,`server3` 收到 `server1` 的投票结果后发现比自己的合适于是更改投票为(1,99)然后广播出去,最后 `server1` 收到了发现自己的投票已经超过半数就把自己设为 `Leader`,`server3` 也随之变为 `Follower`。
+
+> 请注意 `ZooKeeper` 为什么要设置奇数个结点?比如这里我们是三个,挂了一个我们还能正常工作,挂了两个我们就不能正常工作了(已经没有超过半数的节点数了,所以无法进行投票等操作了)。而假设我们现在有四个,挂了一个也能工作,**但是挂了两个也不能正常工作了**,这是和三个一样的,而三个比四个还少一个,带来的效益是一样的,所以 `Zookeeper` 推荐奇数个 `server` 。
+
+那么说完了 `ZAB` 中的 `Leader` 选举方式之后我们再来了解一下 **崩溃恢复** 是什么玩意?
+
+其实主要就是 **当集群中有机器挂了,我们整个集群如何保证数据一致性?**
+
+如果只是 `Follower` 挂了,而且挂的没超过半数的时候,因为我们一开始讲了在 `Leader` 中会维护队列,所以不用担心后面的数据没接收到导致数据不一致性。
+
+如果 `Leader` 挂了那就麻烦了,我们肯定需要先暂停服务变为 `Looking` 状态然后进行 `Leader` 的重新选举(上面我讲过了),但这个就要分为两种情况了,分别是 **确保已经被Leader提交的提案最终能够被所有的Follower提交** 和 **跳过那些已经被丢弃的提案** 。
+
+确保已经被Leader提交的提案最终能够被所有的Follower提交是什么意思呢?
+
+假设 `Leader (server2)` 发送 `commit` 请求(忘了请看上面的消息广播模式),他发送给了 `server3`,然后要发给 `server1` 的时候突然挂了。这个时候重新选举的时候我们如果把 `server1` 作为 `Leader` 的话,那么肯定会产生数据不一致性,因为 `server3` 肯定会提交刚刚 `server2` 发送的 `commit` 请求的提案,而 `server1` 根本没收到所以会丢弃。
+
+
+
+那怎么解决呢?
+
+聪明的同学肯定会质疑,**这个时候 `server1` 已经不可能成为 `Leader` 了,因为 `server1` 和 `server3` 进行投票选举的时候会比较 `ZXID` ,而此时 `server3` 的 `ZXID` 肯定比 `server1` 的大了**。(不理解可以看前面的选举算法)
+
+那么跳过那些已经被丢弃的提案又是什么意思呢?
+
+假设 `Leader (server2)` 此时同意了提案N1,自身提交了这个事务并且要发送给所有 `Follower` 要 `commit` 的请求,却在这个时候挂了,此时肯定要重新进行 `Leader` 的选举,比如说此时选 `server1` 为 `Leader` (这无所谓)。但是过了一会,这个 **挂掉的 `Leader` 又重新恢复了** ,此时它肯定会作为 `Follower` 的身份进入集群中,需要注意的是刚刚 `server2` 已经同意提交了提案N1,但其他 `server` 并没有收到它的 `commit` 信息,所以其他 `server` 不可能再提交这个提案N1了,这样就会出现数据不一致性问题了,所以 **该提案N1最终需要被抛弃掉** 。
+
+
+
+## Zookeeper的几个理论知识
+
+了解了 `ZAB` 协议还不够,它仅仅是 `Zookeeper` 内部实现的一种方式,而我们如何通过 `Zookeeper` 去做一些典型的应用场景呢?比如说集群管理,分布式锁,`Master` 选举等等。
+
+这就涉及到如何使用 `Zookeeper` 了,但在使用之前我们还需要掌握几个概念。比如 `Zookeeper` 的 **数据模型** 、**会话机制**、**ACL**、**Watcher机制** 等等。
+
+### 数据模型
+
+`zookeeper` 数据存储结构与标准的 `Unix` 文件系统非常相似,都是在根节点下挂很多子节点(树型)。但是 `zookeeper` 中没有文件系统中目录与文件的概念,而是 **使用了 `znode` 作为数据节点** 。`znode` 是 `zookeeper` 中的最小数据单元,每个 `znode` 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。
+
+
+
+每个 `znode` 都有自己所属的 **节点类型** 和 **节点状态**。
+
+其中节点类型可以分为 **持久节点**、**持久顺序节点**、**临时节点** 和 **临时顺序节点**。
+
+* 持久节点:一旦创建就一直存在,直到将其删除。
+* 持久顺序节点:一个父节点可以为其子节点 **维护一个创建的先后顺序** ,这个顺序体现在 **节点名称** 上,是节点名称后自动添加一个由 10 位数字组成的数字串,从 0 开始计数。
+* 临时节点:临时节点的生命周期是与 **客户端会话** 绑定的,**会话消失则节点消失** 。临时节点 **只能做叶子节点** ,不能创建子节点。
+* 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。
+
+节点状态中包含了很多节点的属性比如 `czxid` 、`mzxid` 等等,在 `zookeeper` 中是使用 `Stat` 这个类来维护的。下面我列举一些属性解释。
+
+* `czxid`:`Created ZXID`,该数据节点被 **创建** 时的事务ID。
+* `mzxid`:`Modified ZXID`,节点 **最后一次被更新时** 的事务ID。
+* `ctime`:`Created Time`,该节点被创建的时间。
+* `mtime`: `Modified Time`,该节点最后一次被修改的时间。
+* `version`:节点的版本号。
+* `cversion`:**子节点** 的版本号。
+* `aversion`:节点的 `ACL` 版本号。
+* `ephemeralOwner`:创建该节点的会话的 `sessionID` ,如果该节点为持久节点,该值为0。
+* `dataLength`:节点数据内容的长度。
+* `numChildre`:该节点的子节点个数,如果为临时节点为0。
+* `pzxid`:该节点子节点列表最后一次被修改时的事务ID,注意是子节点的 **列表** ,不是内容。
+
+### 会话
+
+我想这个对于后端开发的朋友肯定不陌生,不就是 `session` 吗?只不过 `zk` 客户端和服务端是通过 **`TCP` 长连接** 维持的会话机制,其实对于会话来说你可以理解为 **保持连接状态** 。
+
+在 `zookeeper` 中,会话还有对应的事件,比如 `CONNECTION_LOSS 连接丢失事件` 、`SESSION_MOVED 会话转移事件` 、`SESSION_EXPIRED 会话超时失效事件` 。
+
+### ACL
+
+`ACL` 为 `Access Control Lists` ,它是一种权限控制。在 `zookeeper` 中定义了5种权限,它们分别为:
+
+* `CREATE` :创建子节点的权限。
+* `READ`:获取节点数据和子节点列表的权限。
+* `WRITE`:更新节点数据的权限。
+* `DELETE`:删除子节点的权限。
+* `ADMIN`:设置节点 ACL 的权限。
+
+### Watcher机制
+
+`Watcher` 为事件监听器,是 `zk` 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端 **注册** 指定的 `watcher` ,当服务端符合了 `watcher` 的某些事件或要求则会 **向客户端发送事件通知** ,客户端收到通知后找到自己定义的 `Watcher` 然后 **执行相应的回调方法** 。
+
+
+
+## Zookeeper的几个典型应用场景
+
+前面说了这么多的理论知识,你可能听得一头雾水,这些玩意有啥用?能干啥事?别急,听我慢慢道来。
+
+
+
+### 选主
+
+还记得上面我们的所说的临时节点吗?因为 `Zookeeper` 的强一致性,能够很好地在保证 **在高并发的情况下保证节点创建的全局唯一性** (即无法重复创建同样的节点)。
+
+利用这个特性,我们可以 **让多个客户端创建一个指定的节点** ,创建成功的就是 `master`。
+
+但是,如果这个 `master` 挂了怎么办???
+
+你想想为什么我们要创建临时节点?还记得临时节点的生命周期吗?`master` 挂了是不是代表会话断了?会话断了是不是意味着这个节点没了?还记得 `watcher` 吗?我们是不是可以 **让其他不是 `master` 的节点监听节点的状态** ,比如说我们监听这个临时节点的父节点,如果子节点个数变了就代表 `master` 挂了,这个时候我们 **触发回调函数进行重新选举** ,或者我们直接监听节点的状态,我们可以通过节点是否已经失去连接来判断 `master` 是否挂了等等。
+
+
+
+总的来说,我们可以完全 **利用 临时节点、节点状态 和 `watcher` 来实现选主的功能**,临时节点主要用来选举,节点状态和`watcher` 可以用来判断 `master` 的活性和进行重新选举。
+
+### 分布式锁
+
+分布式锁的实现方式有很多种,比如 `Redis` 、数据库 、`zookeeper` 等。个人认为 `zookeeper` 在实现分布式锁这方面是非常非常简单的。
+
+上面我们已经提到过了 **zk在高并发的情况下保证节点创建的全局唯一性**,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。
+
+如何实现呢?这玩意其实跟选主基本一样,我们也可以利用临时节点的创建来实现。
+
+首先肯定是如何获取锁,因为创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,**创建成功的就说明获取到了锁** 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 `watcher` 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。
+
+> `zk` 中不需要向 `redis` 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。是不是很简答?
+
+那能不能使用 `zookeeper` 同时实现 **共享锁和独占锁** 呢?答案是可以的,不过稍微有点复杂而已。
+
+还记得 **有序的节点** 吗?
+
+这个时候我规定所有创建节点必须有序,当你是读请求(要获取共享锁)的话,如果 **没有比自己更小的节点,或比自己小的节点都是读请求** ,则可以获取到读锁,然后就可以开始读了。**若比自己小的节点中有写请求** ,则当前客户端无法获取到读锁,只能等待前面的写请求完成。
+
+如果你是写请求(获取独占锁),若 **没有比自己更小的节点** ,则表示当前客户端可以直接获取到写锁,对数据进行修改。若发现 **有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁** ,等待所有前面的操作完成。
+
+这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 **羊群效应** 。此时你可以通过让等待的节点只监听他们前面的节点。
+
+具体怎么做呢?其实也很简单,你可以让 **读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点** ,感兴趣的小伙伴可以自己去研究一下。
+
+### 命名服务
+
+如何给一个对象设置ID,大家可能都会想到 `UUID`,但是 `UUID` 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 `zookeeper` 来实现呢?
+
+我们之前提到过 `zookeeper` 是通过 **树形结构** 来存储数据节点的,那也就是说,对于每个节点的 **全路径**,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。
+
+### 集群管理和注册中心
+
+看到这里是不是觉得 `zookeeper` 实在是太强大了,它怎么能这么能干!
+
+别急,它能干的事情还很多呢。可能我们会有这样的需求,我们需要了解整个集群中有多少机器在工作,我们想对及群众的每台机器的运行时状态进行数据采集,对集群中机器进行上下线操作等等。
+
+而 `zookeeper` 天然支持的 `watcher` 和 临时节点能很好的实现这些需求。我们可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 `watcher` 进行状态监控和回调。
+
+
+
+至于注册中心也很简单,我们同样也是让 **服务提供者** 在 `zookeeper` 中创建一个临时节点并且将自己的 `ip、port、调用方式` 写入节点,当 **服务消费者** 需要进行调用的时候会 **通过注册中心找到相应的服务的地址列表(IP端口什么的)** ,并缓存到本地(方便以后调用),当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。
+
+当服务提供者的某台服务器宕机或下线时,相应的地址会从服务提供者地址列表中移除。同时,注册中心会将新的服务地址列表发送给服务消费者的机器并缓存在消费者本机(当然你可以让消费者进行节点监听,我记得 `Eureka` 会先试错,然后再更新)。
+
+
+
+## 总结
+
+看到这里的同学实在是太有耐心了👍👍👍,如果觉得我写得不错的话点个赞哈。
+
+不知道大家是否还记得我讲了什么😒。
+
+
+
+这篇文章中我带大家入门了 `zookeeper` 这个强大的分布式协调框架。现在我们来简单梳理一下整篇文章的内容。
+
+* 分布式与集群的区别
+
+* `2PC` 、`3PC` 以及 `paxos` 算法这些一致性框架的原理和实现。
+
+* `zookeeper` 专门的一致性算法 `ZAB` 原子广播协议的内容(`Leader` 选举、崩溃恢复、消息广播)。
+
+* `zookeeper` 中的一些基本概念,比如 `ACL`,数据节点,会话,`watcher`机制等等。
+
+* `zookeeper` 的典型应用场景,比如选主,注册中心等等。
+
+ 如果忘了可以回去看看再次理解一下,如果有疑问和建议欢迎提出🤝🤝🤝。
\ No newline at end of file
diff --git a/docs/system-design/framework/ZooKeeper.md b/docs/system-design/framework/ZooKeeper.md
index fa6d2b65..4b10ca0c 100644
--- a/docs/system-design/framework/ZooKeeper.md
+++ b/docs/system-design/framework/ZooKeeper.md
@@ -47,8 +47,6 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在
综上,何必增加那一个不必要的zookeeper呢?
-
-
## 二 关于 ZooKeeper 的一些重要概念
### 2.1 重要概念总结
@@ -165,8 +163,8 @@ ZAB协议包括两种基本的模式,分别是 **崩溃恢复和消息广播**
关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章:
-- [图解 Paxos 一致性协议](http://blog.xiaohansong.com/2016/09/30/Paxos/)
-- [Zookeeper ZAB 协议分析](http://blog.xiaohansong.com/2016/08/25/zab/)
+- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/)
+- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html)
关于如何使用 zookeeper 实现分布式锁,可以查看下面这篇文章:
diff --git a/docs/system-design/framework/ZooKeeper数据模型和常见命令.md b/docs/system-design/framework/ZooKeeper数据模型和常见命令.md
index 401c752f..eceaf229 100644
--- a/docs/system-design/framework/ZooKeeper数据模型和常见命令.md
+++ b/docs/system-design/framework/ZooKeeper数据模型和常见命令.md
@@ -26,9 +26,7 @@ ZNode(数据节点)是 ZooKeeper 中数据的最小单元,每个ZNode上
提到 ZooKeeper 数据模型,还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID(Atomic:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)四大特性我在这里就不多说了,相信大家也已经挺腻了。
-在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,**ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。
-
-
+在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。**对于每一个事务请求,ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。
### ZNode(数据节点)的结构
diff --git a/docs/system-design/framework/mybatis/mybatis-interview.md b/docs/system-design/framework/mybatis/mybatis-interview.md
new file mode 100644
index 00000000..877d79dd
--- /dev/null
+++ b/docs/system-design/framework/mybatis/mybatis-interview.md
@@ -0,0 +1,169 @@
+> 本篇文章是JavaGuide收集自网络,原出处不明。
+
+Mybatis 技术内幕系列博客,从原理和源码角度,介绍了其内部实现细节,无论是写的好与不好,我确实是用心写了,由于并不是介绍如何使用 Mybatis 的文章,所以,一些参数使用细节略掉了,我们的目标是介绍 Mybatis 的技术架构和重要组成部分,以及基本运行原理。
+
+博客写的很辛苦,但是写出来却不一定好看,所谓开始很兴奋,过程很痛苦,结束很遗憾。要求不高,只要读者能从系列博客中,学习到一点其他博客所没有的技术点,作为作者,我就很欣慰了,我也读别人写的博客,通常对自己当前研究的技术,是很有帮助的。
+
+尽管还有很多可写的内容,但是,我认为再写下去已经没有意义,任何其他小的功能点,都是在已经介绍的基本框架和基本原理下运行的,只有结束,才能有新的开始。写博客也积攒了一些经验,源码多了感觉就是复制黏贴,源码少了又觉得是空谈原理,将来再写博客,我希望是“精炼博文”,好读好懂美观读起来又不累,希望自己能再写一部开源分布式框架原理系列博客。
+
+有胆就来,我出几道 Mybatis 面试题,看你能回答上来几道(都是我出的,可不是网上找的)。
+
+#### 1、#{}和\${}的区别是什么?
+
+注:这道题是面试官面试我同事的。
+
+答:
+
+- `${}`是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如\${driver}会被静态替换为`com.mysql.jdbc.Driver`。
+- `#{}`是 sql 的参数占位符,Mybatis 会将 sql 中的`#{}`替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),`#{item.name}` 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 `param.getItem().getName()`。
+
+#### 2、Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
+
+注:这道题是京东面试官面试我时问的。
+
+答:还有很多其他的标签,``、``、``、``、``,加上动态 sql 的 9 个标签,`trim|where|set|foreach|if|choose|when|otherwise|bind`等,其中为 sql 片段标签,通过``标签引入 sql 片段,`