From a7b72b6767d469e6771fc19eec8187676c5e27a2 Mon Sep 17 00:00:00 2001 From: RiChard Date: Thu, 7 Dec 2017 18:11:39 +0800 Subject: [PATCH 01/47] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d85f6b7..cf884b8 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,13 @@ HashMap的并发问题 cloneable接口实现原理,浅拷贝or深拷贝 Java NIO使用 hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决 -arraylist和linkedlist区别及实现原理 -反射中,Class.forName和ClassLoader区别 +arraylist和linkedlist区别及实现原理 +* 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 +* 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 +* 然后说实现方法,arrayList的添加,是先判断容器是否容量足够,初始化是10个,注意的是,容量(capacity)跟集合的元素个数(size)不是一个概念,容量一般是大于元素个数的,如果容量不够,则会自动扩容,也就是大概扩容50%左右,不能说肯定扩容50%,公式是:新容量 = 原容量 + (原容量 >> 1),然后调用工具类Arrays的copyof方法,实现数组之间的复制,而copyof方法调用的是System的copyof方法,System再调就是c/c++库了,我没办法看到System的具体实现方法。其实arrayList的remove方法也很巧妙,也是通过数组之间的复制来实现元素的删除的,具体的我忘了,而linkedList的add方法,只是修改前后两个元素的引用。删除方法同理,也就是改引用,arraylist则可能是移动元素的位置。而set和get方法,arraylist直接通过下标获取或者修改相应的值,linkedlist则需要从头或者尾开始遍历,至于从哪里开始遍历,它也是有公式的,就是比较index和(size >> 1)大小,集合右移一位,即接近中间值的值,如果索引比中间值大,则从尾部开始遍历,如果索引比中间值小,则从头部开始遍历。 +* 最后还有一些判断的方法,contain方法调用的是indexof方法,indexof方法里面也是遍历集合加判断元素是否存在的,不存在则返回-1。 + +反射中,Class.forName和ClassLoader区别   String,Stringbuffer,StringBuilder的区别? 有没有可能2个不相等的对象有相同的hashcode 简述NIO的最佳实践,比如netty,mina From cd6f161f7ad272cc4faad6cade2972fa1d1be4e4 Mon Sep 17 00:00:00 2001 From: RiChard Date: Fri, 8 Dec 2017 17:29:30 +0800 Subject: [PATCH 02/47] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf884b8..915842e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ HashMap的并发问题 反射的原理,反射创建类实例的三种方式是什么? cloneable接口实现原理,浅拷贝or深拷贝 Java NIO使用 -hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决 +hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? +* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,node(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element. arraylist和linkedlist区别及实现原理 * 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 * 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 From efea624195c70b85e560b829ea28a4e7a03d993d Mon Sep 17 00:00:00 2001 From: RiChard Date: Fri, 8 Dec 2017 17:50:46 +0800 Subject: [PATCH 03/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 915842e..f5e04fc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ HashMap的并发问题 cloneable接口实现原理,浅拷贝or深拷贝 Java NIO使用 hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? -* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,node(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element. +* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry. arraylist和linkedlist区别及实现原理 * 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 * 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 From 1ec5d4a0bca068eda79469c5ef9f8d3e36afee20 Mon Sep 17 00:00:00 2001 From: RiChard Date: Fri, 8 Dec 2017 17:52:56 +0800 Subject: [PATCH 04/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5e04fc..ff7143a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ HashMap的并发问题 cloneable接口实现原理,浅拷贝or深拷贝 Java NIO使用 hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? -* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry. +* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry.
arraylist和linkedlist区别及实现原理 * 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 * 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 From 7ab52ed21189b0a43086364ce0cce6c57e929b8d Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 14:00:24 +0800 Subject: [PATCH 05/47] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff7143a..aa40d77 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ ## java基础 -Arrays.sort实现原理和Collection实现原理 -foreach和while的区别(编译之后) +1.Arrays.sort实现原理和Collections.sort实现原理.
+答:Collections.sort方法底层会Arrays.sort方法,底层实现都是TimeSort实现的,这是jdk1.7新增的,以前是归并排序。TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来.
+foreach和while的区别(编译之后)   线程池的种类,区别和使用场景 分析线程池的实现原理和线程的调度过程 线程池如何调优 From d333bd1dfe96beceac3ca3aed68eb244d55d8e90 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 16:26:54 +0800 Subject: [PATCH 06/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa40d77..24d6ca1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## java基础 1.Arrays.sort实现原理和Collections.sort实现原理.
-答:Collections.sort方法底层会Arrays.sort方法,底层实现都是TimeSort实现的,这是jdk1.7新增的,以前是归并排序。TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来.
+答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
foreach和while的区别(编译之后)   线程池的种类,区别和使用场景 分析线程池的实现原理和线程的调度过程 From 77fa7a8efb5cb1120405a2dce9418e6fc9208d56 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 16:29:22 +0800 Subject: [PATCH 07/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24d6ca1..839a360 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 1.Arrays.sort实现原理和Collections.sort实现原理.
答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
-foreach和while的区别(编译之后)   +foreach和while的区别(编译之后)  
线程池的种类,区别和使用场景 分析线程池的实现原理和线程的调度过程 线程池如何调优 From 5048a1cadc53be3d89e7c5c339bbed4563d9bd84 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 17:10:16 +0800 Subject: [PATCH 08/47] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 839a360..c3295f6 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ 1.Arrays.sort实现原理和Collections.sort实现原理.
答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
-foreach和while的区别(编译之后)  
-线程池的种类,区别和使用场景 +2、foreach和while的区别(编译之后)  
+foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。 
+线程池的种类,区别和使用场景   分析线程池的实现原理和线程的调度过程 线程池如何调优 线程池的最大线程数目根据什么确定 From 53796702ce84cfd9305a2a9b0f6078e0a79386fc Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 17:10:34 +0800 Subject: [PATCH 09/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3295f6..e678384 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 1.Arrays.sort实现原理和Collections.sort实现原理.
答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
2、foreach和while的区别(编译之后)  
-foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。 
+答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。 
线程池的种类,区别和使用场景   分析线程池的实现原理和线程的调度过程 线程池如何调优 From 95f97a1cf4061f831de99690c18ef8f9b5410591 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 12 Dec 2017 17:13:27 +0800 Subject: [PATCH 10/47] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e678384..028c95c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ## java基础 -1.Arrays.sort实现原理和Collections.sort实现原理.
+#### 1.Arrays.sort实现原理和Collections.sort实现原理.
答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
-2、foreach和while的区别(编译之后)  
+#### 2、foreach和while的区别(编译之后)  
答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。 
线程池的种类,区别和使用场景   分析线程池的实现原理和线程的调度过程 From 936df4e0be213b9d2aab576f38c2f4ec2e3e1cf9 Mon Sep 17 00:00:00 2001 From: RiChard Date: Wed, 13 Dec 2017 16:12:12 +0800 Subject: [PATCH 11/47] Update README.md --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 028c95c..e0e8023 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ ## java基础 -#### 1.Arrays.sort实现原理和Collections.sort实现原理.
-答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。
-#### 2、foreach和while的区别(编译之后)  
-答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。 
-线程池的种类,区别和使用场景   -分析线程池的实现原理和线程的调度过程 -线程池如何调优 +#### 1.Arrays.sort实现原理和Collections.sort实现原理. +答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。 +#### 2、foreach和while的区别(编译之后)   +答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。  +#### 3、线程池的种类,区别和使用场景   +答:常见的有四种,newCacheThreadPool,核心线程数初始化为0,最大线程数为Integer的最大值,非核心空闲线程的存活时间为60s,队列使用的是零缓存队列,任务来的时候直接给线程执行,不会阻塞,使用场景:执行时间短的异步任务。newFixThreadPool核心线程数等于最大线程数,其实最大线程数并没有多大作用,因为队列使用了LinkedBlockingQueue无界队列,所以当核心线程数小于任务数的时候,没有被执行的任务全部放在队列里,如果任务量足够大,就可能撑爆内存,非核心空闲线程的存活时间为0s,使用场景:执行时间长的任务,newScheduledThreadPool,周期线程池,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0s,workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列,使用场景为周期性执行的任务,newSingleThreadExecutor,核心线程数和最大线程数都为一,非核心空闲线程的存活时间为0s,队列使用的是LinkedBlockingQueue无界队列,使用场景为一个一个的任务要有序执行。 +#### 4、分析线程池的实现原理和线程的调度过程 +答: + +线程池如何调优   线程池的最大线程数目根据什么确定 动态代理的几种方式 HashMap的并发问题 From f6158633779e00ac81e2868e7e21efd370948af0 Mon Sep 17 00:00:00 2001 From: RiChard Date: Fri, 15 Dec 2017 18:07:16 +0800 Subject: [PATCH 12/47] Update README.md --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e0e8023..37ef2e0 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,21 @@ #### 2、foreach和while的区别(编译之后)   答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。  #### 3、线程池的种类,区别和使用场景   -答:常见的有四种,newCacheThreadPool,核心线程数初始化为0,最大线程数为Integer的最大值,非核心空闲线程的存活时间为60s,队列使用的是零缓存队列,任务来的时候直接给线程执行,不会阻塞,使用场景:执行时间短的异步任务。newFixThreadPool核心线程数等于最大线程数,其实最大线程数并没有多大作用,因为队列使用了LinkedBlockingQueue无界队列,所以当核心线程数小于任务数的时候,没有被执行的任务全部放在队列里,如果任务量足够大,就可能撑爆内存,非核心空闲线程的存活时间为0s,使用场景:执行时间长的任务,newScheduledThreadPool,周期线程池,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0s,workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列,使用场景为周期性执行的任务,newSingleThreadExecutor,核心线程数和最大线程数都为一,非核心空闲线程的存活时间为0s,队列使用的是LinkedBlockingQueue无界队列,使用场景为一个一个的任务要有序执行。 +答:常见的有四种,这四种都是通过Executors静态工厂创建的,newCacheThreadPool,核心线程数初始化为0,最大线程数为Integer的最大值,非核心空闲线程的存活时间为60s,队列使用的是零缓存队列,任务来的时候直接给线程执行,不会阻塞,使用场景:执行时间短的异步任务。newFixThreadPool核心线程数等于最大线程数,其实最大线程数并没有多大作用,因为队列使用了LinkedBlockingQueue无界队列,所以当核心线程数小于任务数的时候,没有被执行的任务全部放在队列里,如果任务量足够大,就可能撑爆内存,非核心空闲线程的存活时间为0s,使用场景:执行时间长的任务,newScheduledThreadPool,周期线程池,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0s,workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列,使用场景为周期性执行的任务,newSingleThreadExecutor,核心线程数和最大线程数都为一,非核心空闲线程的存活时间为0s,队列使用的是LinkedBlockingQueue无界队列,使用场景为一个一个的任务要有序执行。 #### 4、分析线程池的实现原理和线程的调度过程 -答: - -线程池如何调优   +答:线程池有什么好处?我们为什么要用线程池?线程的创建和销毁是很耗资源的,这就希望线程能够被很好的管理、复用。所以线程池的作用就出来了,分别是:减少资源的损耗;提高响应速度,避免了线程创建和销毁不要的时间;方便统一管理线程。
+线程池的实现原理和调度线程的过程:线程池的创建,会初始化5到7个参数(核心线程数,最大线程数,线程存活时间,存活时间单位,队列类型,拒绝策略类型(四种:抛异常、默认会选择这种策略;直接丢弃提交过来的任务、不处理;直接让调用方调用,不经过线程池;丢弃最老的任务,也就是队列的头元素,执行提高过来的第一个任务),线程工厂)。
+线程池的执行(任务):也就是线程池的实现原理,可以调用submit或者execute方法,submit方法底层就是调用execute方法,不过submit方法会返回一个FatureTask对象,而execute不会有返回值,这个对象调用get方法如果返回值是null,则说明任务执行完成。而execute方法的底层是怎么实现的呢?任务提交过来的时候(先打住一下,任务其实就是一个Runnable,执行线程只不过封装了这个任务。执行的时候调用runWorker方法,也就是runworker方法调用了任务的run方法,回来再细说这个worker类和runworker方法。),先判断任务数是否小于核心线程数,如果小于核心线程数,那么就创建一个线程扔到线程池中(如何创建?就是调用addWorker方法,回头再细说),如果大于核心线程数,并且队列没有满(我们先假设是有界队列),那么将任务存放到队列中,如果队列满了,但任务数没有超过最大线程数,那么就创建一个线程,放到线程池(HashSet存放一个个Worker,执行线程)中,如果任务超过最大线程数,那么就进行四种拒绝策略的其中一种,默认是抛异常。
+线程池的关闭:有两种关闭线程池的方法,分别是shutdown和shutdownNow方法。区别在哪里呢?shutdown方法分两步,先是设置线程的状态为shutdown,这个值是0向右移动29高位,这种状态会让执行线程不接受提交过来的任务,但是会执行队列里及之前的任务;然后是对空闲线程执行interrupt方法。如何判断是否为空闲非核心线程呢?空闲非核心线程一般不会上锁的,核心线程会上锁,所以只要获取是否已经上锁便可知道该线程是否为空闲非核心线程。什么是空闲非核心线程呢?空闲非核心线程就是worker的task属性为null,也可以理解为没有任务去执行,但是还存活在线程池中的非核心线程,我们知道核心线程一般不会被销毁,除非调用shutdownNow方法或者设置允许核心线程超时为true,这时候就可能被销毁。而shutdownNow方法是先设置线程状态(有五种,这个下面细说)为stop,stop的值为1先左移动29位,这种状态线程不接受提交过来的任务,并立即不处理所有的任务。然后让线程池里所有的执行线程调用interrupt方法。
+上面已经说完线程池的实现原理和线程的调度过程,下面主要讲一下细节上的:
+先说worker类,worker类里面的封装有task任务,执行线程,状态为负一,表示上锁,其他线程获取不到这个worker,还有一个run方法,里面调用了task的run方法。
+addworker方法,主要的是判断线程池的状态,线程池的容量及核心数,如果线程池的状态为Running,并且任务数小于线程池的容量或者小于线程数的核心线程数,那么就是让workers,也就是hashSet里面add一个worker。
+runWorker方法,获取worker的属性任务,然后调用任务的run方法。
+getTask方法,判断是都允许核心线程数超时或者当前任务数大于核心线程数,如果是,则调用阻塞队列的poll方法,否则则调用阻塞队列take方法。
+线程池的五种状态:分别是running(可接收提交的任务,执行线程可处理任务,调用shutdown方法进入shutdown状态,调用shutdownNow方法会进入stop状态),shutdown(不可接收提交的任务,执行线程可处理任务,调用shutdownNow方法会进入stop状态),stop(不可接收提交的任务,执行线程不可处理任务,),tidying(当stop状态的执行线程数为null并且队列没有任务时,调用tryTerminate方法进入terminated状态),terminated(对执行线程和队列任务会回收完毕). + + +##### 线程池如何调优   线程池的最大线程数目根据什么确定 动态代理的几种方式 HashMap的并发问题 From 2aab3b7db39a4d3987b45679416b9cbcca0f3d18 Mon Sep 17 00:00:00 2001 From: RiChard Date: Sat, 16 Dec 2017 15:52:54 +0800 Subject: [PATCH 13/47] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37ef2e0..e718763 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 线程池的五种状态:分别是running(可接收提交的任务,执行线程可处理任务,调用shutdown方法进入shutdown状态,调用shutdownNow方法会进入stop状态),shutdown(不可接收提交的任务,执行线程可处理任务,调用shutdownNow方法会进入stop状态),stop(不可接收提交的任务,执行线程不可处理任务,),tidying(当stop状态的执行线程数为null并且队列没有任务时,调用tryTerminate方法进入terminated状态),terminated(对执行线程和队列任务会回收完毕). -##### 线程池如何调优   -线程池的最大线程数目根据什么确定 +#### 5、线程池如何调优 + +#### 6、线程池的最大线程数目根据什么确定 动态代理的几种方式 HashMap的并发问题 了解LinkedHashMap的应用吗 From bcfd6d979d4eeeb388dc270c8f34761fd89656f0 Mon Sep 17 00:00:00 2001 From: RiChard Date: Mon, 18 Dec 2017 11:17:07 +0800 Subject: [PATCH 14/47] Update README.md --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e718763..3efa397 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,19 @@ runWorker方法,获取worker的属性任务,然后调用任务的run方法 getTask方法,判断是都允许核心线程数超时或者当前任务数大于核心线程数,如果是,则调用阻塞队列的poll方法,否则则调用阻塞队列take方法。
线程池的五种状态:分别是running(可接收提交的任务,执行线程可处理任务,调用shutdown方法进入shutdown状态,调用shutdownNow方法会进入stop状态),shutdown(不可接收提交的任务,执行线程可处理任务,调用shutdownNow方法会进入stop状态),stop(不可接收提交的任务,执行线程不可处理任务,),tidying(当stop状态的执行线程数为null并且队列没有任务时,调用tryTerminate方法进入terminated状态),terminated(对执行线程和队列任务会回收完毕). - #### 5、线程池如何调优 +我们为什么要调优线程池?就是在任务并发处理的时候,让用户在获取数据少等待。一般来说,用户如果超过三秒或者是五秒之内,页面还没有完全加载完成,就会关闭页面。如果让用户有一个很好的体验,建议在2秒之内。所以性能优化方面(这里先特指后台上的线程池)必须要做足功课。线程池调优无非是设置线程池初始化时的那几个参数。
+核心线程数  = (并发数的范围)* 单个任务的处理时间。如果一秒钟并发的个数为500到1000个,每个任务的处理完成平均时间为0.1s,那么核心线程数设置为50到100 +之间,建议设置为75个到80个。
+队列容量:队列常见的有无界队列、有界队列、零缓存队列等。不建议选择无界队列。因为任务进入队列会等待,而且处理的时间无法准确的把握,等待的时间就会在2s或者3s的基础上叠加。建议选择有界队列或者零缓存队列(任务不会在队列中等待,直接提交给线程处理)。如果让等待时间为1s后提交到线程中处理。那么个数和核心线程数相等。75到80之间。任务等待的时间越长,用户叠加等待的时间也就越长,建议在是使用队列的时候,队列容量越小越好。
+最大线程数 = (最大并发任务数-队列里的任务数)* 任务平均处理时间。如果使用零缓存队列,队列的任务数为0,最大并发任务数(每秒钟)假设为1000,任务平均处理时间为0.1s,那么最大线程数为100。当然如果硬件允许的情况下,可以设置再大一些。
+线程存活时间一般为60s,这个默认值大小就可以。
+拒绝策略:一般默认是抛拒绝执行异常,如果业务允许的话,也可以选择其中的不处理或者其他。
+最后较重要的是硬件方面,任务分发等。硬件不行升级硬件。任务分发不合理再重新调整权重。
+ +#### 6、线程池的最大线程数目根据什么确定   +公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. -#### 6、线程池的最大线程数目根据什么确定 动态代理的几种方式 HashMap的并发问题 了解LinkedHashMap的应用吗 From 6430d09f27f94b6bbb8a92472fe36daba36a79f2 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 19 Dec 2017 16:09:31 +0800 Subject: [PATCH 15/47] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3efa397..22e0a65 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,10 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 #### 6、线程池的最大线程数目根据什么确定   公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. -动态代理的几种方式 -HashMap的并发问题 +#### 7、动态代理的几种方式 +1、动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。 +jdk动态代理的代理类必须实现invacationHandler接口,目标类必须是实现其接口。jdk动态代理的关键在字节码生成的那一块,编译字节码的那个类要继承Proxy接口和实现目标类的接口,目标方法会调用代理类的invoke方法,invoke方法会传进一个目标方法,这个目标方法是通过Class.forName(xxx).getMethod(xxx)得到的。 +HashMap的并发问题   了解LinkedHashMap的应用吗 反射的原理,反射创建类实例的三种方式是什么? cloneable接口实现原理,浅拷贝or深拷贝 From 9c4d4a2e370653b53f8f8435a6f1e0ea7aa9d804 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 19 Dec 2017 16:11:41 +0800 Subject: [PATCH 16/47] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 22e0a65..00b1000 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,14 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 #### 7、动态代理的几种方式 1、动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。 jdk动态代理的代理类必须实现invacationHandler接口,目标类必须是实现其接口。jdk动态代理的关键在字节码生成的那一块,编译字节码的那个类要继承Proxy接口和实现目标类的接口,目标方法会调用代理类的invoke方法,invoke方法会传进一个目标方法,这个目标方法是通过Class.forName(xxx).getMethod(xxx)得到的。 -HashMap的并发问题   -了解LinkedHashMap的应用吗 -反射的原理,反射创建类实例的三种方式是什么? -cloneable接口实现原理,浅拷贝or深拷贝 -Java NIO使用 -hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? -* 答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry.
-arraylist和linkedlist区别及实现原理 +#### 8、HashMap的并发问题   +#### 9、了解LinkedHashMap的应用吗 +#### 10、反射的原理,反射创建类实例的三种方式是什么? +#### 11、cloneable接口实现原理,浅拷贝or深拷贝 +#### 12、Java NIO使用 +#### 13、hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? +答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry.
+#### 14、arraylist和linkedlist区别及实现原理 * 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 * 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 * 然后说实现方法,arrayList的添加,是先判断容器是否容量足够,初始化是10个,注意的是,容量(capacity)跟集合的元素个数(size)不是一个概念,容量一般是大于元素个数的,如果容量不够,则会自动扩容,也就是大概扩容50%左右,不能说肯定扩容50%,公式是:新容量 = 原容量 + (原容量 >> 1),然后调用工具类Arrays的copyof方法,实现数组之间的复制,而copyof方法调用的是System的copyof方法,System再调就是c/c++库了,我没办法看到System的具体实现方法。其实arrayList的remove方法也很巧妙,也是通过数组之间的复制来实现元素的删除的,具体的我忘了,而linkedList的add方法,只是修改前后两个元素的引用。删除方法同理,也就是改引用,arraylist则可能是移动元素的位置。而set和get方法,arraylist直接通过下标获取或者修改相应的值,linkedlist则需要从头或者尾开始遍历,至于从哪里开始遍历,它也是有公式的,就是比较index和(size >> 1)大小,集合右移一位,即接近中间值的值,如果索引比中间值大,则从尾部开始遍历,如果索引比中间值小,则从头部开始遍历。 From b98dd828723a82230dafb150456991fa2d2fdee2 Mon Sep 17 00:00:00 2001 From: RiChard Date: Tue, 19 Dec 2017 17:44:25 +0800 Subject: [PATCH 17/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00b1000..4dcd7e9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. #### 7、动态代理的几种方式 -1、动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。 +1、动态代理也称运行时增强技术,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。 jdk动态代理的代理类必须实现invacationHandler接口,目标类必须是实现其接口。jdk动态代理的关键在字节码生成的那一块,编译字节码的那个类要继承Proxy接口和实现目标类的接口,目标方法会调用代理类的invoke方法,invoke方法会传进一个目标方法,这个目标方法是通过Class.forName(xxx).getMethod(xxx)得到的。 #### 8、HashMap的并发问题   #### 9、了解LinkedHashMap的应用吗 From 52a6fd561e5f1f4b72dca33fb24760f0d98602e3 Mon Sep 17 00:00:00 2001 From: RiChard Date: Wed, 20 Dec 2017 15:31:31 +0800 Subject: [PATCH 18/47] Update README.md --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4dcd7e9..008cf14 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,13 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 #### 6、线程池的最大线程数目根据什么确定   公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. -#### 7、动态代理的几种方式 -1、动态代理也称运行时增强技术,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。 -jdk动态代理的代理类必须实现invacationHandler接口,目标类必须是实现其接口。jdk动态代理的关键在字节码生成的那一块,编译字节码的那个类要继承Proxy接口和实现目标类的接口,目标方法会调用代理类的invoke方法,invoke方法会传进一个目标方法,这个目标方法是通过Class.forName(xxx).getMethod(xxx)得到的。 +#### 7、动态代理的几种方式(如何实现?缓存?字节码?反射机制还是fastClass机制?) +1、动态代理也称运行时增强技术,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
+ +jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法这个目标方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
+cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写intercept方法,获取最终代理类过程是创建一个增强器,然后设置目标类,设置拦截对象,最后调用增强器对象的create方法返回一个代理类。
+缓存:它们都将数据以key-value的形式存在缓存中,获取代理类对象一般先判断缓存中是否存在,如果不存在才通过方法反射(jdk动态代理)或者方法索引(cglib )的方式得到。
+字节码底层:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
#### 8、HashMap的并发问题   #### 9、了解LinkedHashMap的应用吗 #### 10、反射的原理,反射创建类实例的三种方式是什么? From e955446b0cee38f3e571cc503d41acf3a07b06ec Mon Sep 17 00:00:00 2001 From: RiChard Date: Wed, 20 Dec 2017 15:37:38 +0800 Subject: [PATCH 19/47] Update README.md --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 008cf14..fd61661 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,16 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 #### 7、动态代理的几种方式(如何实现?缓存?字节码?反射机制还是fastClass机制?) 1、动态代理也称运行时增强技术,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
-jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法这个目标方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
+jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
+ cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写intercept方法,获取最终代理类过程是创建一个增强器,然后设置目标类,设置拦截对象,最后调用增强器对象的create方法返回一个代理类。
+ 缓存:它们都将数据以key-value的形式存在缓存中,获取代理类对象一般先判断缓存中是否存在,如果不存在才通过方法反射(jdk动态代理)或者方法索引(cglib )的方式得到。
-字节码底层:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
+ +字节码底层:cglib是继承目标类和实现工厂接口,jdk动态代理是继承Proxy类和实现目标类的接口。它们最重要的相同点是最终代理类的最终目标方法会调用各自的拦截方法,invoke或者intercept方法。 + +反射机制还是fastClass机制:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
+ #### 8、HashMap的并发问题   #### 9、了解LinkedHashMap的应用吗 #### 10、反射的原理,反射创建类实例的三种方式是什么? From 2db7fa81cef411cafc2956306ac2a404adb00a13 Mon Sep 17 00:00:00 2001 From: RiChard Date: Thu, 21 Dec 2017 09:02:31 +0800 Subject: [PATCH 20/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd61661..b0c55c1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ getTask方法,判断是都允许核心线程数超时或者当前任务数大 公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. #### 7、动态代理的几种方式(如何实现?缓存?字节码?反射机制还是fastClass机制?) -1、动态代理也称运行时增强技术,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
+1、动态代理也称运行时增强技术,是spring框架的aop模块的实现原理,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
From 8ef19a028536eeed951deec4ebcf001d65a22ba4 Mon Sep 17 00:00:00 2001 From: RiChard Date: Thu, 21 Dec 2017 10:52:34 +0800 Subject: [PATCH 21/47] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b0c55c1..2f113e8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写interce 反射机制还是fastClass机制:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
#### 8、HashMap的并发问题   +https://coolshell.cn/articles/9606.html +当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。 + #### 9、了解LinkedHashMap的应用吗 #### 10、反射的原理,反射创建类实例的三种方式是什么? #### 11、cloneable接口实现原理,浅拷贝or深拷贝 From 456be6c11dc6e3b082933092fcb098e82ef64485 Mon Sep 17 00:00:00 2001 From: RiChard Date: Thu, 21 Dec 2017 11:11:22 +0800 Subject: [PATCH 22/47] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f113e8..158bf69 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,13 @@ cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写interce 反射机制还是fastClass机制:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
#### 8、HashMap的并发问题   -https://coolshell.cn/articles/9606.html -当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。 +分析地址:https://coolshell.cn/articles/9606.html
+当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。
+HashMap死循环演示
+假如有两个线程P1、P2,以及链表 a=》b=》null
+1、P1先执行,执行完"Entry next = e.next;"代码后发生阻塞,或者其他情况不再执行下去,此时e=a,next=b
+2、而P2已经执行完整段代码,于是当前的新链表newTable[i]为b=》a=》null
+3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
#### 9、了解LinkedHashMap的应用吗 #### 10、反射的原理,反射创建类实例的三种方式是什么? From d2da52c507c18ede7e65d0cb02be83d391eaaa75 Mon Sep 17 00:00:00 2001 From: RiChard Date: Sun, 24 Dec 2017 14:35:59 +0800 Subject: [PATCH 23/47] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 158bf69..63c2170 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ HashMap死循环演示
3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
#### 9、了解LinkedHashMap的应用吗 -#### 10、反射的原理,反射创建类实例的三种方式是什么? +LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双端队列的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 +#### 10、反射的原理,反射创建类实例的三种方式是什么? #### 11、cloneable接口实现原理,浅拷贝or深拷贝 #### 12、Java NIO使用 #### 13、hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? From 7f70a2cef780bb098e957c9f9cf9019e70590743 Mon Sep 17 00:00:00 2001 From: RiChard Date: Sun, 24 Dec 2017 14:38:23 +0800 Subject: [PATCH 24/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63c2170..e20f4c5 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ HashMap死循环演示
3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
#### 9、了解LinkedHashMap的应用吗 -LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双端队列的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 +LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问(调用get或者put方法)顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双向链表的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 #### 10、反射的原理,反射创建类实例的三种方式是什么? #### 11、cloneable接口实现原理,浅拷贝or深拷贝 #### 12、Java NIO使用 From 550f95cea74c21c4008b2bef8e8cea1eba3fc5b6 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Thu, 15 Mar 2018 21:02:29 +0800 Subject: [PATCH 25/47] Update README.md --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e20f4c5..6825de1 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,16 @@ TreeMap的实现原理 ### JVM相关 -类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序 -JVM内存分代 +#### 1、类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序   + * 父类静态变量 --> 父类的静态代码块 --> + 子类静态变量--> 子类的静态代码块 --> + 父类字段 --> 父类代码块1 --> 父类代码块2(2写在1下面) --> 父类构造函数 + 子类字段 --> 子类代码块1 --> 子类代码块2(2写在1下面) --> 子类构造函数 + +#### 2、JVM内存分代   +* 这里的内存是指的是堆内存,而不是非堆内存,堆内存(自己写的代码运行时存储的区域)和非堆内存,也称堆外内存,NIO部分就引用到堆外内存,这部分是直接和系统打交道的,当然native相关的代码也在运行时存储在这里。下面正式谈谈堆内存分代,堆内存分为新生代(三分之一)、老年代(三分之二)、还有永久代( +先不讨论永久代)。新生代里面又分eden区,from区(survivor1),to区(survivor2),对象创建一般先分到Eden区,但大对象直接分到老年代。eden区:from区:to区的空间比例是8:1:1.新生代的垃圾收集算法是复制算法,老年的垃圾收集算法是标记整理算法。当eden区的空间不足以存下新创建的对象时,会进行一次minor GC.存活的对象和survivor1区(from区)的对象复制到to区。然后清空eden区和from区。这时from区和to区进行转换,from变成to,to变成from.而存活的对象年龄加一,大于15岁这个默认值的话,就会认为是长期存活的对象,进到老年代。minor GC回收频率高,速度快。如果to区空间不足,那将触发空间担保机制。将一部分对象存到老年代,如果老年代空间也不足,那就会导致空间担保失败,进行一次Major GC .而进行一次Major GC一般会触发一次Minor GC.触发Major GC的条件还有直接调用System.gc();老年代空间不足等等。Major GC回收频率相对较小,回收时间长。 + Java 8的内存分代改进 JVM垃圾回收机制,何时触发MinorGC等操作 jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等 From c13c0ad0d162b55087f89b717390c3dc6d5c3ef9 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Sun, 18 Mar 2018 22:51:28 +0800 Subject: [PATCH 26/47] Update README.md --- README.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6825de1..74a648e 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,32 @@ TreeMap的实现原理 * 这里的内存是指的是堆内存,而不是非堆内存,堆内存(自己写的代码运行时存储的区域)和非堆内存,也称堆外内存,NIO部分就引用到堆外内存,这部分是直接和系统打交道的,当然native相关的代码也在运行时存储在这里。下面正式谈谈堆内存分代,堆内存分为新生代(三分之一)、老年代(三分之二)、还有永久代( 先不讨论永久代)。新生代里面又分eden区,from区(survivor1),to区(survivor2),对象创建一般先分到Eden区,但大对象直接分到老年代。eden区:from区:to区的空间比例是8:1:1.新生代的垃圾收集算法是复制算法,老年的垃圾收集算法是标记整理算法。当eden区的空间不足以存下新创建的对象时,会进行一次minor GC.存活的对象和survivor1区(from区)的对象复制到to区。然后清空eden区和from区。这时from区和to区进行转换,from变成to,to变成from.而存活的对象年龄加一,大于15岁这个默认值的话,就会认为是长期存活的对象,进到老年代。minor GC回收频率高,速度快。如果to区空间不足,那将触发空间担保机制。将一部分对象存到老年代,如果老年代空间也不足,那就会导致空间担保失败,进行一次Major GC .而进行一次Major GC一般会触发一次Minor GC.触发Major GC的条件还有直接调用System.gc();老年代空间不足等等。Major GC回收频率相对较小,回收时间长。 -Java 8的内存分代改进 -JVM垃圾回收机制,何时触发MinorGC等操作 -jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等 -你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1 -新生代和老生代的内存回收策略 -Eden和Survivor的比例分配等 -深入分析了Classloader,双亲委派机制 +#### 3、Java 8的内存分代改进 +* 永久代 - > 元空间。 +它们之间的区别是:元空间不在虚拟机中,而是使用系统内存。 +使用元空间有什么好处呢?为什么要换成元空间? +原因: +字符串等字面量容易在永久代的运行时常量池中溢出。 +类及方法的信息等在永久代中比较难确定其大小。 +永久代在虚拟机中的回收效率偏低。 + +#### 4、JVM垃圾回收机制,何时触发MinorGC等操作 +* 同2 +#### 5、jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等 +* 同2 +* 大对象:PreTenureSizeThresHold +长期存活的对象:MaxTenuringThresHold +SurvivorRatio + +#### 6、你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1 +* 翻书 + +#### 7、新生代和老生代的内存回收策略 +* 同2 +#### 8、Eden和Survivor的比例分配等   +* 默认8:1:1 + +深入分析了Classloader,双亲委派机制   JVM的编译优化 对Java内存模型的理解,以及其在并发中的应用 指令重排序,内存栅栏等 From 58bb9513492ee2852b3cef903c36a5711cced524 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Wed, 21 Mar 2018 09:53:09 +0800 Subject: [PATCH 27/47] =?UTF-8?q?Create=20GC=E5=9C=A8=E4=BB=80=E4=B9=88?= =?UTF-8?q?=E6=97=B6=E5=80=99=E5=AF=B9=E4=BB=80=E4=B9=88=E4=B8=9C=E8=A5=BF?= =?UTF-8?q?=E5=81=9A=E4=BA=86=E4=BB=80=E4=B9=88=E4=BA=8B=E6=83=85.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...200\344\271\210\344\272\213\346\203\205.md" | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 "GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" diff --git "a/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" new file mode 100644 index 0000000..945216f --- /dev/null +++ "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" @@ -0,0 +1,18 @@ +GC在什么时候,对什么东西,做了什么事情? +答:什么时候?这个时间不好确定。 +但有一点肯定的是,eden区满了的时候会进行MinorGC。 +人为的调用System.GC()的时候对象总大小大于老年代的剩余空间的时候,空间担保失败并且在HandlePromotionFailure开关开启的时候,会进行fullGC。 +当然我们也可以通过NewRatio开关控制新生代和老年代的分配空间的比例,让老年代的空间大些, +设置MaxTenuringThreshold参数让达到进入老年的对象存活次数大些,从而通过空间的大小使得 +垃圾回收的时候向后延长。 +对什么东西? +通过可达性分析,从GC Root向下搜索不到,经过第一次标记清理后,仍然没有复活的对象。 +做了什么事情? +删除不使用的对象,回收内存空间。 +新生代会采用复制算法清理。 +新生代分eden区和两个survivor区(from,to),eden:from:to = 8:1:1, +每次使用只用eden区和一个from区,eden区和from区存活的对象复制到区,然后清空 +eden区和from区,最后from区和to区替换。 +老年代会采用标记清理算法清理。 +标记GC root向下搜索到的存活对象, +清理是遍历堆中所有的对象,清理没有被标记的对象 From 3977dcb489ebe1d436df9c89301648b5413f36ba Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Wed, 21 Mar 2018 09:53:35 +0800 Subject: [PATCH 28/47] =?UTF-8?q?Update=20GC=E5=9C=A8=E4=BB=80=E4=B9=88?= =?UTF-8?q?=E6=97=B6=E5=80=99=E5=AF=B9=E4=BB=80=E4=B9=88=E4=B8=9C=E8=A5=BF?= =?UTF-8?q?=E5=81=9A=E4=BA=86=E4=BB=80=E4=B9=88=E4=BA=8B=E6=83=85.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\206\344\273\200\344\271\210\344\272\213\346\203\205.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" index 945216f..508037f 100644 --- "a/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" +++ "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" @@ -1,5 +1,5 @@ -GC在什么时候,对什么东西,做了什么事情? -答:什么时候?这个时间不好确定。 +##### GC在什么时候,对什么东西,做了什么事情? +* 答:什么时候?这个时间不好确定。 但有一点肯定的是,eden区满了的时候会进行MinorGC。 人为的调用System.GC()的时候对象总大小大于老年代的剩余空间的时候,空间担保失败并且在HandlePromotionFailure开关开启的时候,会进行fullGC。 当然我们也可以通过NewRatio开关控制新生代和老年代的分配空间的比例,让老年代的空间大些, From 317751249fbcedb32f61d481918c1ed7c81ea31c Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Wed, 21 Mar 2018 09:53:59 +0800 Subject: [PATCH 29/47] =?UTF-8?q?Update=20GC=E5=9C=A8=E4=BB=80=E4=B9=88?= =?UTF-8?q?=E6=97=B6=E5=80=99=E5=AF=B9=E4=BB=80=E4=B9=88=E4=B8=9C=E8=A5=BF?= =?UTF-8?q?=E5=81=9A=E4=BA=86=E4=BB=80=E4=B9=88=E4=BA=8B=E6=83=85.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...00\344\271\210\344\272\213\346\203\205.md" | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git "a/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" index 508037f..54651cc 100644 --- "a/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" +++ "b/GC\345\234\250\344\273\200\344\271\210\346\227\266\345\200\231\345\257\271\344\273\200\344\271\210\344\270\234\350\245\277\345\201\232\344\272\206\344\273\200\344\271\210\344\272\213\346\203\205.md" @@ -1,18 +1,18 @@ ##### GC在什么时候,对什么东西,做了什么事情? * 答:什么时候?这个时间不好确定。 -但有一点肯定的是,eden区满了的时候会进行MinorGC。 -人为的调用System.GC()的时候对象总大小大于老年代的剩余空间的时候,空间担保失败并且在HandlePromotionFailure开关开启的时候,会进行fullGC。 -当然我们也可以通过NewRatio开关控制新生代和老年代的分配空间的比例,让老年代的空间大些, -设置MaxTenuringThreshold参数让达到进入老年的对象存活次数大些,从而通过空间的大小使得 -垃圾回收的时候向后延长。 -对什么东西? -通过可达性分析,从GC Root向下搜索不到,经过第一次标记清理后,仍然没有复活的对象。 -做了什么事情? -删除不使用的对象,回收内存空间。 -新生代会采用复制算法清理。 -新生代分eden区和两个survivor区(from,to),eden:from:to = 8:1:1, -每次使用只用eden区和一个from区,eden区和from区存活的对象复制到区,然后清空 -eden区和from区,最后from区和to区替换。 -老年代会采用标记清理算法清理。 -标记GC root向下搜索到的存活对象, -清理是遍历堆中所有的对象,清理没有被标记的对象 +* 但有一点肯定的是,eden区满了的时候会进行MinorGC。 +* 人为的调用System.GC()的时候对象总大小大于老年代的剩余空间的时候,空间担保失败并且在HandlePromotionFailure开关开启的时候,会进行fullGC。 +* 当然我们也可以通过NewRatio开关控制新生代和老年代的分配空间的比例,让老年代的空间大些, +* 设置MaxTenuringThreshold参数让达到进入老年的对象存活次数大些,从而通过空间的大小使得 +* 垃圾回收的时候向后延长。 +* 对什么东西? +* 通过可达性分析,从GC Root向下搜索不到,经过第一次标记清理后,仍然没有复活的对象。 +* 做了什么事情? +* 删除不使用的对象,回收内存空间。 +* 新生代会采用复制算法清理。 +* 新生代分eden区和两个survivor区(from,to),eden:from:to = 8:1:1, +* 每次使用只用eden区和一个from区,eden区和from区存活的对象复制到区,然后清空 +* eden区和from区,最后from区和to区替换。 +* 老年代会采用标记清理算法清理。 +* 标记GC root向下搜索到的存活对象, +* 清理是遍历堆中所有的对象,清理没有被标记的对象 From ff287f27cc04e9c99e605ddb62a007f371087802 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Thu, 22 Mar 2018 15:16:59 +0800 Subject: [PATCH 30/47] =?UTF-8?q?Create=20ThreadLocal=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E8=A7=A3=E6=9E=90.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\220\347\240\201\350\247\243\346\236\220.md" | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 "ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" diff --git "a/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" new file mode 100644 index 0000000..6d8f719 --- /dev/null +++ "b/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -0,0 +1,17 @@ +#### 什么是ThreadLocal? +* ThreadLocal这个类位于lang包下的,专门用于解决在高并发环境下,对共享变量的访问导致线程出现不安全的问题。也就是通过线程内部副本变量的方式。 +#### 那ThreadLocal内部的结构是怎么样的,如何保证线程安全的? +* ThreadLocal内部维护着一个ThreadLocalMap,这个map是ThreadLocal的静态内部类,静态内部类保证了线程访问内部类属性的安全, +里面存储的是键值对。key一般是线程的id,value就是我们想要的目的值。这样做的好处就是保证每一个线程维护自己的局部映射表。 +而ThreadLocalMap是如何解决散列冲突的,也可以说我们知道的hash碰撞。我们知道,解决hash碰撞有两种方式,一种是链表的方式。 +HashMap内部就是使用这种方式解决hash碰撞的,也就是对key进行hashcode以后得到的存储的地址,如果有值存在,就在值的后面添加 +一条小尾巴,就是链表,下次我们可以通过遍历链表查询得到我们想要的值,而另外一种方式就是通过开放地址法,什么是开放地址法,就是 +对key进行hashcode后得到的内存地址有值后,就向后位移,找到空位坐下,因为一般hashcode比较均匀,所以才采用这种方式。到了数组的 +末尾还没有找到空位,就返回头部继续找空位。那这个hash值为什么会这么均匀呢?它是通过automicInteger和一个固定的值累加上去的。添加 +一对key-value,automicInteger就自动加一。 +#### ThreadLocal的应用场景? +一般是数据库连接的时候和管理Session的时候。因为数据库如何大量连接而且很多是重复的ip地址的话,数据库容易崩溃的。 +Session也是。 +#### ThreadLocal内存泄漏的原因以及我们应该如何解决? +* ThreadLocal内存泄漏的原因是ThreadlocalMap是静态修饰的,生命周期和ThreadLocal一样长,如果用完没有手动删除对应的key就可能导致内存泄漏。 +所以预防的策略就是要养成一个好习惯,用完之后也就是获取了之后及时remove对应key的数据。 From 275f120e0e947005f2d4f3766fbc5fd0871de1ea Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Thu, 22 Mar 2018 15:17:20 +0800 Subject: [PATCH 31/47] =?UTF-8?q?Update=20ThreadLocal=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E8=A7=A3=E6=9E=90.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...eadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" index 6d8f719..b492c5f 100644 --- "a/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" +++ "b/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -10,7 +10,7 @@ HashMap内部就是使用这种方式解决hash碰撞的,也就是对key进行 末尾还没有找到空位,就返回头部继续找空位。那这个hash值为什么会这么均匀呢?它是通过automicInteger和一个固定的值累加上去的。添加 一对key-value,automicInteger就自动加一。 #### ThreadLocal的应用场景? -一般是数据库连接的时候和管理Session的时候。因为数据库如何大量连接而且很多是重复的ip地址的话,数据库容易崩溃的。 +* 一般是数据库连接的时候和管理Session的时候。因为数据库如何大量连接而且很多是重复的ip地址的话,数据库容易崩溃的。 Session也是。 #### ThreadLocal内存泄漏的原因以及我们应该如何解决? * ThreadLocal内存泄漏的原因是ThreadlocalMap是静态修饰的,生命周期和ThreadLocal一样长,如果用完没有手动删除对应的key就可能导致内存泄漏。 From 5b1ad35860f0fa25f6c22a00f577962af8b727df Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Sat, 24 Mar 2018 18:46:54 +0800 Subject: [PATCH 32/47] =?UTF-8?q?Create=20java=E4=B8=ADhashMap=E7=9A=84?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=A4=A7=E5=B0=8F=E4=B8=BA=E4=BB=80=E4=B9=88?= =?UTF-8?q?=E6=98=AF2=E7=9A=84=E5=B9=82.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...3\200\344\271\210\346\230\2572\347\232\204\345\271\202.md" | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 "java\344\270\255hashMap\347\232\204\351\273\230\350\256\244\345\244\247\345\260\217\344\270\272\344\273\200\344\271\210\346\230\2572\347\232\204\345\271\202.md" diff --git "a/java\344\270\255hashMap\347\232\204\351\273\230\350\256\244\345\244\247\345\260\217\344\270\272\344\273\200\344\271\210\346\230\2572\347\232\204\345\271\202.md" "b/java\344\270\255hashMap\347\232\204\351\273\230\350\256\244\345\244\247\345\260\217\344\270\272\344\273\200\344\271\210\346\230\2572\347\232\204\345\271\202.md" new file mode 100644 index 0000000..1bf2b72 --- /dev/null +++ "b/java\344\270\255hashMap\347\232\204\351\273\230\350\256\244\345\244\247\345\260\217\344\270\272\344\273\200\344\271\210\346\230\2572\347\232\204\345\271\202.md" @@ -0,0 +1,4 @@ +#### java中hashMap的默认大小为什么是2的幂? +* 提高空间的利用率,尽可能的减少hash碰撞,或者说是散列冲突。比如:我们设定hasmap的容量为15,那么调用hash方法的时候,那个公式没记错的, +原来的hash值&(容量-1),如果是15那么出来的hash值尾数肯定是1,而entry是不会占用这样的位置的,所以导致尾数为1的位置的空间剩余,导致资源的 +浪费,也尽可能会出现hash碰撞,降低程序的运行效率。 From 4afc0b4d177f224d8b6bcf0abfb32ceb681e67e7 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Mon, 26 Mar 2018 17:34:31 +0800 Subject: [PATCH 33/47] =?UTF-8?q?Create=20ConcurrentHashMap=E6=BA=90?= =?UTF-8?q?=E7=A0=81=E5=88=86=E6=9E=90.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\347\240\201\345\210\206\346\236\220.txt" | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 "ConcurrentHashMap\346\272\220\347\240\201\345\210\206\346\236\220.txt" diff --git "a/ConcurrentHashMap\346\272\220\347\240\201\345\210\206\346\236\220.txt" "b/ConcurrentHashMap\346\272\220\347\240\201\345\210\206\346\236\220.txt" new file mode 100644 index 0000000..7a385c2 --- /dev/null +++ "b/ConcurrentHashMap\346\272\220\347\240\201\345\210\206\346\236\220.txt" @@ -0,0 +1,74 @@ +package com.testconcurrent; + +import java.util.concurrent.ConcurrentHashMap; + +public class ChmTest { + /** + * 补充二叉树和红黑树相关知识。 + * 二叉树特点:左边子节点(值大小)<= 父节点 <= 右边子节点 + * 二叉树思想:二分查找 + * 二叉树缺点:无法保证平衡左右子节点个数,通俗的说:比如左边节点的个数远远大于右边节点的个数。 + * 弥补二叉树的缺点:红黑树(可保证根节点到叶节点的最大长度小于等于最小长度的两倍) + * 红黑树保持左右节点个数平衡的五大规则: + * 1、节点不是黑色就是红色的。 + * 2、根节点必须是黑色的。 + * 3、叶节点(NUL节点或者是空节点)必须是黑色的。 + * 4、红色节点下的子节点必须黑色的。 (重要,必须记住) + * 5、从任一一个节点向下查找,直到叶节点的所有路径里的黑色节点都相等。 (重要,必须记住) + * 变色:黑色变成红色或者红色变成黑色。 + * 旋转方式有两种: + * 左旋:http://img.blog.csdn.net/20170323102309404?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU3VuX1RUVFQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast + * 右旋:http://img.blog.csdn.net/20170323102237896?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU3VuX1RUVFQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast + * 原地址:https://blog.csdn.net/sun_tttt/article/details/65445754 + * 那什么时候会进行旋转和变色呢?插入和删除元素时,破坏上面五大规则的时候。旋转和变色有可能组合进行,但最终得到的红黑树都必须是符合上面五大规则的。 + */ + public static void main(String[] args) { + + //概念:在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry数组组成,即数组+链表+红黑树 + //Segment数组的意义就是锁分离技术 + + //初始化: + //ConcurrentHashMap的初始化是会通过位与运算来初始化Segment的大小,用ssize来表示 ,一般默认ssize = 1; + //因为ssize用位于运算来计算(ssize <<=1),所以Segment的大小取值都是以2的N次方 + //HashEntry大小的计算也是2的N次方(cap <<=1), cap的初始值为1,所以HashEntry默认最小的容量为2 + ConcurrentHashMap chm = new ConcurrentHashMap(); + + //put操作 + //第一步:初始化Segment: + //Segment实现了ReentrantLock,也就带有锁的功能 + //当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值 + //第二步:初始化HashEntry + //先找位置:segment的hash值&( 位与运算)表的长度-1 + //然后尝试获取Segment锁,如果获取到,直接插入,如果获取不到,则进行自旋(while(!tryLock())),达到一定的次数,将等待唤醒 + chm.put(123, "123"); + + //get操作 + //获取对应的segment的锁,如果没有获取到,则返回null,获取到则通过hash key定位到segment的位置。 + //然后定位到HashEntry,遍历比较获取值 + System.err.println(chm.get(123)); + + //size操作(两种方案) + //第一种方案:三次获取值比较,相同则没有改变,直接返回 + //第二种方案:第一种方案不成立,则给所有segment加锁,然后取值,返回 + System.out.println(chm.size()); + chm.clear();//调用segment数组的clear方法 + chm.contains("123");//先上segment锁,在遍历hashEntry的链表 + chm.containsKey(123);//先上segment锁,在遍历hashEntry的数组 + //...还有很多方法,这就不一一分析,实现不复杂 + + //jdk1.8和1.7的ConcurrentHashMap的区别 + //一、1.8的ConcurrentHashMap锁的粒度会更细,1.7的chm(ConcurrentHashMap)采用segment+HashEntry+红黑树实现, + //一个segment包含多个HashEntry,锁为segment(继承ReentrantLock) + //而1.8将抛弃segment,采用CAS+Node(HashEntry)+synchronized+红黑树实现,锁的粒度为HashEntry + + //1.8的put方法 + //是否正在扩容?正在扩容,则先扩容。 + //是否有hash冲突?即检查插入点有没数据,如果没有数据,则直接CAS插入(调用CompareAndSwapInt方法),如果有hash冲突,则加锁,等待插入。 + //插入方式的选择?hashentry的方式有两种,一种是直接插入链表的尾端,另外一种是根据红黑树的特点调整插入,选择那一种方案取决于链表的长度,是否大于等于8 + //是否需要扩容?多条线程进行扩容。 + + //1.8的get方法 + //检查是否只有一个头结点?有则直接返回。没有则获取锁,直接遍历返回. + //hashentry链表长度不是一的时候,还得检查是否正在扩容?如果是,则调用节点的find方法,遍历之后返回 + } +} From 9856f6ae5cf43b34de08b0babef310d4425be6b4 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Tue, 3 Apr 2018 13:40:47 +0800 Subject: [PATCH 34/47] =?UTF-8?q?Create=20spring=E7=9A=84=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=80=9D=E6=83=B3=E5=92=8C=E5=8E=9F=E7=90=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...263\345\222\214\345\216\237\347\220\206.md" | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 "spring\347\232\204\345\256\236\347\216\260\346\200\235\346\203\263\345\222\214\345\216\237\347\220\206.md" diff --git "a/spring\347\232\204\345\256\236\347\216\260\346\200\235\346\203\263\345\222\214\345\216\237\347\220\206.md" "b/spring\347\232\204\345\256\236\347\216\260\346\200\235\346\203\263\345\222\214\345\216\237\347\220\206.md" new file mode 100644 index 0000000..2be0892 --- /dev/null +++ "b/spring\347\232\204\345\256\236\347\216\260\346\200\235\346\203\263\345\222\214\345\216\237\347\220\206.md" @@ -0,0 +1,18 @@ +spring基本原理其实就是通过反射解析类及其类的各种信息,包括构造器、 +方法及其参数,属性。然后将其封装成bean定义信息类、constructor信息类、 +method信息类、property信息类,最终放在一个map里, +也就是所谓的container,池等等,其实就是个map。。汗。。。。 + +当你写好配置文件,启动项目后,框架会先按照你的配置文件找到那个要scan的 +包,然后解析包里面的所有类,找到所有含有@bean,@service等注解的类, +利用反射解析它们,包括解析构造器,方法,属性等等,然后封装成各种信息 +类放到一个map里。每当你需要一个bean的时候,框架就会从container找是不 +是有这个类的定义啊?如果找到则通过构造器new出来(这就是控制反转DI,不用你new, +框架帮你new), + +再在这个类找是不是有要注入的属性或者方法,比如标有@autowired的 +属性,如果有则还是到container找对应的解析类,new出对象,并通过之前解析出来的 +信息类找到setter方法,然后用该方法注入对象(这就是依赖注入IOC)。 + +动态代理(AOP),也称运行时增强。它的思想是面向切面编程的,比如在一个方法执行时,我们 +想在方法执行的前后增加另外一些方法,让它和这个方法一同执行,那么这种技术就是动态代理。 From 083aa24fbd03acc1f01c71005e82e7c9c6332bdf Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:09:39 +0800 Subject: [PATCH 35/47] =?UTF-8?q?Create=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...1\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" | 1 + 1 file changed, 1 insertion(+) create mode 100644 "\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" new file mode 100644 index 0000000..8b13789 --- /dev/null +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -0,0 +1 @@ + From 6e668190cc115240e3ecf81aac1da10e164e14a6 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:10:54 +0800 Subject: [PATCH 36/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...30\344\270\216\347\255\224\346\241\210.md" | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index 8b13789..19970bb 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -1 +1,55 @@ +#### 1.Arrays.sort实现原理和Collections.sort实现原理. +答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。 +#### 2、foreach和while的区别(编译之后)   +答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。  +#### 3、线程池的种类,区别和使用场景   +答:常见的有四种,这四种都是通过Executors静态工厂创建的,newCacheThreadPool,核心线程数初始化为0,最大线程数为Integer的最大值,非核心空闲线程的存活时间为60s,队列使用的是零缓存队列,任务来的时候直接给线程执行,不会阻塞,使用场景:执行时间短的异步任务。newFixThreadPool核心线程数等于最大线程数,其实最大线程数并没有多大作用,因为队列使用了LinkedBlockingQueue无界队列,所以当核心线程数小于任务数的时候,没有被执行的任务全部放在队列里,如果任务量足够大,就可能撑爆内存,非核心空闲线程的存活时间为0s,使用场景:执行时间长的任务,newScheduledThreadPool,周期线程池,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0s,workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列,使用场景为周期性执行的任务,newSingleThreadExecutor,核心线程数和最大线程数都为一,非核心空闲线程的存活时间为0s,队列使用的是LinkedBlockingQueue无界队列,使用场景为一个一个的任务要有序执行。 +#### 4、分析线程池的实现原理和线程的调度过程 +答:线程池有什么好处?我们为什么要用线程池?线程的创建和销毁是很耗资源的,这就希望线程能够被很好的管理、复用。所以线程池的作用就出来了,分别是:减少资源的损耗;提高响应速度,避免了线程创建和销毁不要的时间;方便统一管理线程。
+线程池的实现原理和调度线程的过程:线程池的创建,会初始化5到7个参数(核心线程数,最大线程数,线程存活时间,存活时间单位,队列类型,拒绝策略类型(四种:抛异常、默认会选择这种策略;直接丢弃提交过来的任务、不处理;直接让调用方调用,不经过线程池;丢弃最老的任务,也就是队列的头元素,执行提高过来的第一个任务),线程工厂)。
+线程池的执行(任务):也就是线程池的实现原理,可以调用submit或者execute方法,submit方法底层就是调用execute方法,不过submit方法会返回一个FatureTask对象,而execute不会有返回值,这个对象调用get方法如果返回值是null,则说明任务执行完成。而execute方法的底层是怎么实现的呢?任务提交过来的时候(先打住一下,任务其实就是一个Runnable,执行线程只不过封装了这个任务。执行的时候调用runWorker方法,也就是runworker方法调用了任务的run方法,回来再细说这个worker类和runworker方法。),先判断任务数是否小于核心线程数,如果小于核心线程数,那么就创建一个线程扔到线程池中(如何创建?就是调用addWorker方法,回头再细说),如果大于核心线程数,并且队列没有满(我们先假设是有界队列),那么将任务存放到队列中,如果队列满了,但任务数没有超过最大线程数,那么就创建一个线程,放到线程池(HashSet存放一个个Worker,执行线程)中,如果任务超过最大线程数,那么就进行四种拒绝策略的其中一种,默认是抛异常。
+线程池的关闭:有两种关闭线程池的方法,分别是shutdown和shutdownNow方法。区别在哪里呢?shutdown方法分两步,先是设置线程的状态为shutdown,这个值是0向右移动29高位,这种状态会让执行线程不接受提交过来的任务,但是会执行队列里及之前的任务;然后是对空闲线程执行interrupt方法。如何判断是否为空闲非核心线程呢?空闲非核心线程一般不会上锁的,核心线程会上锁,所以只要获取是否已经上锁便可知道该线程是否为空闲非核心线程。什么是空闲非核心线程呢?空闲非核心线程就是worker的task属性为null,也可以理解为没有任务去执行,但是还存活在线程池中的非核心线程,我们知道核心线程一般不会被销毁,除非调用shutdownNow方法或者设置允许核心线程超时为true,这时候就可能被销毁。而shutdownNow方法是先设置线程状态(有五种,这个下面细说)为stop,stop的值为1先左移动29位,这种状态线程不接受提交过来的任务,并立即不处理所有的任务。然后让线程池里所有的执行线程调用interrupt方法。
+上面已经说完线程池的实现原理和线程的调度过程,下面主要讲一下细节上的:
+先说worker类,worker类里面的封装有task任务,执行线程,状态为负一,表示上锁,其他线程获取不到这个worker,还有一个run方法,里面调用了task的run方法。
+addworker方法,主要的是判断线程池的状态,线程池的容量及核心数,如果线程池的状态为Running,并且任务数小于线程池的容量或者小于线程数的核心线程数,那么就是让workers,也就是hashSet里面add一个worker。
+runWorker方法,获取worker的属性任务,然后调用任务的run方法。
+getTask方法,判断是都允许核心线程数超时或者当前任务数大于核心线程数,如果是,则调用阻塞队列的poll方法,否则则调用阻塞队列take方法。
+线程池的五种状态:分别是running(可接收提交的任务,执行线程可处理任务,调用shutdown方法进入shutdown状态,调用shutdownNow方法会进入stop状态),shutdown(不可接收提交的任务,执行线程可处理任务,调用shutdownNow方法会进入stop状态),stop(不可接收提交的任务,执行线程不可处理任务,),tidying(当stop状态的执行线程数为null并且队列没有任务时,调用tryTerminate方法进入terminated状态),terminated(对执行线程和队列任务会回收完毕). +#### 5、线程池如何调优 +我们为什么要调优线程池?就是在任务并发处理的时候,让用户在获取数据少等待。一般来说,用户如果超过三秒或者是五秒之内,页面还没有完全加载完成,就会关闭页面。如果让用户有一个很好的体验,建议在2秒之内。所以性能优化方面(这里先特指后台上的线程池)必须要做足功课。线程池调优无非是设置线程池初始化时的那几个参数。
+核心线程数  = (并发数的范围)* 单个任务的处理时间。如果一秒钟并发的个数为500到1000个,每个任务的处理完成平均时间为0.1s,那么核心线程数设置为50到100 +之间,建议设置为75个到80个。
+队列容量:队列常见的有无界队列、有界队列、零缓存队列等。不建议选择无界队列。因为任务进入队列会等待,而且处理的时间无法准确的把握,等待的时间就会在2s或者3s的基础上叠加。建议选择有界队列或者零缓存队列(任务不会在队列中等待,直接提交给线程处理)。如果让等待时间为1s后提交到线程中处理。那么个数和核心线程数相等。75到80之间。任务等待的时间越长,用户叠加等待的时间也就越长,建议在是使用队列的时候,队列容量越小越好。
+最大线程数 = (最大并发任务数-队列里的任务数)* 任务平均处理时间。如果使用零缓存队列,队列的任务数为0,最大并发任务数(每秒钟)假设为1000,任务平均处理时间为0.1s,那么最大线程数为100。当然如果硬件允许的情况下,可以设置再大一些。
+线程存活时间一般为60s,这个默认值大小就可以。
+拒绝策略:一般默认是抛拒绝执行异常,如果业务允许的话,也可以选择其中的不处理或者其他。
+最后较重要的是硬件方面,任务分发等。硬件不行升级硬件。任务分发不合理再重新调整权重。
+ +#### 6、线程池的最大线程数目根据什么确定   +公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. + +#### 7、动态代理的几种方式(如何实现?缓存?字节码?反射机制还是fastClass机制?) +1、动态代理也称运行时增强技术,是spring框架的aop模块的实现原理,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
+ +jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
+ +cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写intercept方法,获取最终代理类过程是创建一个增强器,然后设置目标类,设置拦截对象,最后调用增强器对象的create方法返回一个代理类。
+ +缓存:它们都将数据以key-value的形式存在缓存中,获取代理类对象一般先判断缓存中是否存在,如果不存在才通过方法反射(jdk动态代理)或者方法索引(cglib )的方式得到。
+ +字节码底层:cglib是继承目标类和实现工厂接口,jdk动态代理是继承Proxy类和实现目标类的接口。它们最重要的相同点是最终代理类的最终目标方法会调用各自的拦截方法,invoke或者intercept方法。 + +反射机制还是fastClass机制:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
+ +#### 8、HashMap的并发问题   +分析地址:https://coolshell.cn/articles/9606.html
+当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。
+HashMap死循环演示
+假如有两个线程P1、P2,以及链表 a=》b=》null
+1、P1先执行,执行完"Entry next = e.next;"代码后发生阻塞,或者其他情况不再执行下去,此时e=a,next=b
+2、而P2已经执行完整段代码,于是当前的新链表newTable[i]为b=》a=》null
+3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
+ +#### 9、了解LinkedHashMap的应用吗 +LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问(调用get或者put方法)顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双向链表的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 From ab1451d211d41a288a14c5947d8beb311dc75d02 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:12:59 +0800 Subject: [PATCH 37/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6\351\242\230\344\270\216\347\255\224\346\241\210.md" | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index 19970bb..52a6ed3 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -53,3 +53,12 @@ HashMap死循环演示
#### 9、了解LinkedHashMap的应用吗 LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问(调用get或者put方法)顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双向链表的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 + +#### 13、hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? +答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry.
+#### 14、arraylist和linkedlist区别及实现原理 +* 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 +* 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 +* 然后说实现方法,arrayList的添加,是先判断容器是否容量足够,初始化是10个,注意的是,容量(capacity)跟集合的元素个数(size)不是一个概念,容量一般是大于元素个数的,如果容量不够,则会自动扩容,也就是大概扩容50%左右,不能说肯定扩容50%,公式是:新容量 = 原容量 + (原容量 >> 1),然后调用工具类Arrays的copyof方法,实现数组之间的复制,而copyof方法调用的是System的copyof方法,System再调就是c/c++库了,我没办法看到System的具体实现方法。其实arrayList的remove方法也很巧妙,也是通过数组之间的复制来实现元素的删除的,具体的我忘了,而linkedList的add方法,只是修改前后两个元素的引用。删除方法同理,也就是改引用,arraylist则可能是移动元素的位置。而set和get方法,arraylist直接通过下标获取或者修改相应的值,linkedlist则需要从头或者尾开始遍历,至于从哪里开始遍历,它也是有公式的,就是比较index和(size >> 1)大小,集合右移一位,即接近中间值的值,如果索引比中间值大,则从尾部开始遍历,如果索引比中间值小,则从头部开始遍历。 +* 最后还有一些判断的方法,contain方法调用的是indexof方法,indexof方法里面也是遍历集合加判断元素是否存在的,不存在则返回-1。 + From 48a3070031ba13d49c8c28a76957135856fbd2ca Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:13:42 +0800 Subject: [PATCH 38/47] Update README.md --- README.md | 98 ------------------------------------------------------- 1 file changed, 98 deletions(-) diff --git a/README.md b/README.md index 74a648e..0714af5 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,8 @@ ## java基础 -#### 1.Arrays.sort实现原理和Collections.sort实现原理. -答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。 -#### 2、foreach和while的区别(编译之后)   -答:foreach 一次读取全部内容,while读一次显示一次,对于大数据量的操作建议使用while。  -#### 3、线程池的种类,区别和使用场景   -答:常见的有四种,这四种都是通过Executors静态工厂创建的,newCacheThreadPool,核心线程数初始化为0,最大线程数为Integer的最大值,非核心空闲线程的存活时间为60s,队列使用的是零缓存队列,任务来的时候直接给线程执行,不会阻塞,使用场景:执行时间短的异步任务。newFixThreadPool核心线程数等于最大线程数,其实最大线程数并没有多大作用,因为队列使用了LinkedBlockingQueue无界队列,所以当核心线程数小于任务数的时候,没有被执行的任务全部放在队列里,如果任务量足够大,就可能撑爆内存,非核心空闲线程的存活时间为0s,使用场景:执行时间长的任务,newScheduledThreadPool,周期线程池,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0s,workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列,使用场景为周期性执行的任务,newSingleThreadExecutor,核心线程数和最大线程数都为一,非核心空闲线程的存活时间为0s,队列使用的是LinkedBlockingQueue无界队列,使用场景为一个一个的任务要有序执行。 -#### 4、分析线程池的实现原理和线程的调度过程 -答:线程池有什么好处?我们为什么要用线程池?线程的创建和销毁是很耗资源的,这就希望线程能够被很好的管理、复用。所以线程池的作用就出来了,分别是:减少资源的损耗;提高响应速度,避免了线程创建和销毁不要的时间;方便统一管理线程。
-线程池的实现原理和调度线程的过程:线程池的创建,会初始化5到7个参数(核心线程数,最大线程数,线程存活时间,存活时间单位,队列类型,拒绝策略类型(四种:抛异常、默认会选择这种策略;直接丢弃提交过来的任务、不处理;直接让调用方调用,不经过线程池;丢弃最老的任务,也就是队列的头元素,执行提高过来的第一个任务),线程工厂)。
-线程池的执行(任务):也就是线程池的实现原理,可以调用submit或者execute方法,submit方法底层就是调用execute方法,不过submit方法会返回一个FatureTask对象,而execute不会有返回值,这个对象调用get方法如果返回值是null,则说明任务执行完成。而execute方法的底层是怎么实现的呢?任务提交过来的时候(先打住一下,任务其实就是一个Runnable,执行线程只不过封装了这个任务。执行的时候调用runWorker方法,也就是runworker方法调用了任务的run方法,回来再细说这个worker类和runworker方法。),先判断任务数是否小于核心线程数,如果小于核心线程数,那么就创建一个线程扔到线程池中(如何创建?就是调用addWorker方法,回头再细说),如果大于核心线程数,并且队列没有满(我们先假设是有界队列),那么将任务存放到队列中,如果队列满了,但任务数没有超过最大线程数,那么就创建一个线程,放到线程池(HashSet存放一个个Worker,执行线程)中,如果任务超过最大线程数,那么就进行四种拒绝策略的其中一种,默认是抛异常。
-线程池的关闭:有两种关闭线程池的方法,分别是shutdown和shutdownNow方法。区别在哪里呢?shutdown方法分两步,先是设置线程的状态为shutdown,这个值是0向右移动29高位,这种状态会让执行线程不接受提交过来的任务,但是会执行队列里及之前的任务;然后是对空闲线程执行interrupt方法。如何判断是否为空闲非核心线程呢?空闲非核心线程一般不会上锁的,核心线程会上锁,所以只要获取是否已经上锁便可知道该线程是否为空闲非核心线程。什么是空闲非核心线程呢?空闲非核心线程就是worker的task属性为null,也可以理解为没有任务去执行,但是还存活在线程池中的非核心线程,我们知道核心线程一般不会被销毁,除非调用shutdownNow方法或者设置允许核心线程超时为true,这时候就可能被销毁。而shutdownNow方法是先设置线程状态(有五种,这个下面细说)为stop,stop的值为1先左移动29位,这种状态线程不接受提交过来的任务,并立即不处理所有的任务。然后让线程池里所有的执行线程调用interrupt方法。
-上面已经说完线程池的实现原理和线程的调度过程,下面主要讲一下细节上的:
-先说worker类,worker类里面的封装有task任务,执行线程,状态为负一,表示上锁,其他线程获取不到这个worker,还有一个run方法,里面调用了task的run方法。
-addworker方法,主要的是判断线程池的状态,线程池的容量及核心数,如果线程池的状态为Running,并且任务数小于线程池的容量或者小于线程数的核心线程数,那么就是让workers,也就是hashSet里面add一个worker。
-runWorker方法,获取worker的属性任务,然后调用任务的run方法。
-getTask方法,判断是都允许核心线程数超时或者当前任务数大于核心线程数,如果是,则调用阻塞队列的poll方法,否则则调用阻塞队列take方法。
-线程池的五种状态:分别是running(可接收提交的任务,执行线程可处理任务,调用shutdown方法进入shutdown状态,调用shutdownNow方法会进入stop状态),shutdown(不可接收提交的任务,执行线程可处理任务,调用shutdownNow方法会进入stop状态),stop(不可接收提交的任务,执行线程不可处理任务,),tidying(当stop状态的执行线程数为null并且队列没有任务时,调用tryTerminate方法进入terminated状态),terminated(对执行线程和队列任务会回收完毕). - -#### 5、线程池如何调优 -我们为什么要调优线程池?就是在任务并发处理的时候,让用户在获取数据少等待。一般来说,用户如果超过三秒或者是五秒之内,页面还没有完全加载完成,就会关闭页面。如果让用户有一个很好的体验,建议在2秒之内。所以性能优化方面(这里先特指后台上的线程池)必须要做足功课。线程池调优无非是设置线程池初始化时的那几个参数。
-核心线程数  = (并发数的范围)* 单个任务的处理时间。如果一秒钟并发的个数为500到1000个,每个任务的处理完成平均时间为0.1s,那么核心线程数设置为50到100 -之间,建议设置为75个到80个。
-队列容量:队列常见的有无界队列、有界队列、零缓存队列等。不建议选择无界队列。因为任务进入队列会等待,而且处理的时间无法准确的把握,等待的时间就会在2s或者3s的基础上叠加。建议选择有界队列或者零缓存队列(任务不会在队列中等待,直接提交给线程处理)。如果让等待时间为1s后提交到线程中处理。那么个数和核心线程数相等。75到80之间。任务等待的时间越长,用户叠加等待的时间也就越长,建议在是使用队列的时候,队列容量越小越好。
-最大线程数 = (最大并发任务数-队列里的任务数)* 任务平均处理时间。如果使用零缓存队列,队列的任务数为0,最大并发任务数(每秒钟)假设为1000,任务平均处理时间为0.1s,那么最大线程数为100。当然如果硬件允许的情况下,可以设置再大一些。
-线程存活时间一般为60s,这个默认值大小就可以。
-拒绝策略:一般默认是抛拒绝执行异常,如果业务允许的话,也可以选择其中的不处理或者其他。
-最后较重要的是硬件方面,任务分发等。硬件不行升级硬件。任务分发不合理再重新调整权重。
- -#### 6、线程池的最大线程数目根据什么确定   -公式:线程池的最大线程数 = (最大并发任务数 - 队列容量) * 任务平均响应时间。如果使用零缓存队列,队列容量为0. - -#### 7、动态代理的几种方式(如何实现?缓存?字节码?反射机制还是fastClass机制?) -1、动态代理也称运行时增强技术,是spring框架的aop模块的实现原理,动态代理方式我了解的有:jdk动态代理,cglib动态代理,javassist动态代理。
- -jdk动态代理的代理类必须实现invacationHandler接口,重写invoke方法,获取最终代理类是通过调用Proxy的静态方法newProxyInstance,传入三个参数,分别是目标类的类加载器,目标类的接口数组,和代理类的对象。
- -cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写intercept方法,获取最终代理类过程是创建一个增强器,然后设置目标类,设置拦截对象,最后调用增强器对象的create方法返回一个代理类。
- -缓存:它们都将数据以key-value的形式存在缓存中,获取代理类对象一般先判断缓存中是否存在,如果不存在才通过方法反射(jdk动态代理)或者方法索引(cglib )的方式得到。
- -字节码底层:cglib是继承目标类和实现工厂接口,jdk动态代理是继承Proxy类和实现目标类的接口。它们最重要的相同点是最终代理类的最终目标方法会调用各自的拦截方法,invoke或者intercept方法。 - -反射机制还是fastClass机制:cglib的fastClass机制是对目标类的方法设置索引,然后通过索引直接调用目标类的方法。这样的好处是比jdk动态代理通过对目标类方法的反射,即调用Class.forName(xxx).getMethod(xxx)调用目标类的方法要快一些。
- -#### 8、HashMap的并发问题   -分析地址:https://coolshell.cn/articles/9606.html
-当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。
-HashMap死循环演示
-假如有两个线程P1、P2,以及链表 a=》b=》null
-1、P1先执行,执行完"Entry next = e.next;"代码后发生阻塞,或者其他情况不再执行下去,此时e=a,next=b
-2、而P2已经执行完整段代码,于是当前的新链表newTable[i]为b=》a=》null
-3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
- -#### 9、了解LinkedHashMap的应用吗 -LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问(调用get或者put方法)顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双向链表的尾部,迭代的时候会从尾部到头部的顺序依次迭代。 #### 10、反射的原理,反射创建类实例的三种方式是什么? #### 11、cloneable接口实现原理,浅拷贝or深拷贝 #### 12、Java NIO使用 -#### 13、hashtable和hashmap的区别及实现原理,hashmap会问到数组索引,hash碰撞怎么解决? -答:以jdk1.7为示例,hashtable官方已经不推荐使用了,但是在没有concurrent包之前,hashtable还是做出了很大的贡献,首先hashtable和hashmap的底层都是hash表,数组加链表形式,hashtable线程是安全的,put和get方法都用了synchronized修饰,其次不允许存null值的,效率相对于hashmap要低一些。hashmap线程是不安全的,如果要实现线程安全,可以使用concurrentMap代替或者使用Collections工具类的synchronizeMap封装,哈希表有两种实现方式,一种是开放地址链表来实现,另一种是使用冲突链表,而hashmap使用的就是冲突链表实现的,也就是数组加链表的形式,数组一般用来存key经过hashcode方法的集合,默认容量初始化16个,注意的是容量跟元素个数大小是不一样的概念,源码告诉咋们,一般来说,容量的大小设置为元素个数除于0.75,然后加+1,为什么容量初始化设置为16个呢,原因是让数组的元素分布更加均匀一些,而不会产生更多的hash碰撞呢,如何实现更加均匀呢,就是调用hash函数,hash函数实现高位运算,也就是与运算,公式是key经过hashcode方法返回的值&(容量-1),数学公式: index = HashCode(Key) & (Length - 1),而初始化容量最好是2次幂,这样分布的更加均匀一些,如果容量不够了,也就是元素个数超过了12个,他就会启动扩容机制,增加一倍的容量,代码是原容量左移动一位。扩容是怎么实现的呢?将原来的index重新分配,复制到新容器中,重新分配会耗相对较大的性能,所以一般建议是使用hashmap最好初始化容量,公式是:(元素个数/0.75)+1,这样是最好的,如果不确定容量,最好初始化一个相对较大的容量。put方法和get方法是怎么实现的呢?存元素的时候,先获取key,进行hashcode,得到index值,如果链表为空,则直接存入,而如果链表有元素,比较key的值,相同则覆盖,不相同则采用头插法,排在前面,entry节点的属性(hash,key,values,next,),next属性指向旧节点。同理,get方法也是根据key,hashcode以后得到的值,找到对应的数组下标,然后通过比较key的形式获取element;数组索引就是key的hascode的值,hash碰撞就是当数组索引相同时,数据可能会存重复的情况,而hashmap一般使用链地址的方式解决,也就是索引相同的采用头插法存链表,next指针指向下一个entry.
-#### 14、arraylist和linkedlist区别及实现原理 -* 答:基于jdk1.7,ArrayList的底层是数组,而LinkedList的底层是双向链表。 -* 先说接口,ArrayList的实现的接口有List接口,也就是说它是有序集合,支持存取顺序一致,其次还实现随机访问接口,也就是RandomAccess,但这个接口里面没有要实现的方法,它只是一种暗示,意思就是说数组是带下标的,访问元素的时间复杂度是常数级的,知道下标就可以获取或者设置存取位置的值,但是插入和删除的时间复杂度是线性级的,原因要移动元素。还有实现了可克隆接口,以及序列化接口,也就是说arraylist可以用于对象传输,其次继承了AbstractList接口,里面有一些需要实现的增删改查方法,LinkedList实现了List接口,它也是有序集合,其次还实现双端队列接口,可以充当栈或者队列,但如果用于栈或者队列,ArrayDeque性能更好。LinkedList是双向链表。如果存取的null值,那么头尾指针都指向null,每个node节点头尾都有双向引用,引用也就是c/c++所谓的指针。 -* 然后说实现方法,arrayList的添加,是先判断容器是否容量足够,初始化是10个,注意的是,容量(capacity)跟集合的元素个数(size)不是一个概念,容量一般是大于元素个数的,如果容量不够,则会自动扩容,也就是大概扩容50%左右,不能说肯定扩容50%,公式是:新容量 = 原容量 + (原容量 >> 1),然后调用工具类Arrays的copyof方法,实现数组之间的复制,而copyof方法调用的是System的copyof方法,System再调就是c/c++库了,我没办法看到System的具体实现方法。其实arrayList的remove方法也很巧妙,也是通过数组之间的复制来实现元素的删除的,具体的我忘了,而linkedList的add方法,只是修改前后两个元素的引用。删除方法同理,也就是改引用,arraylist则可能是移动元素的位置。而set和get方法,arraylist直接通过下标获取或者修改相应的值,linkedlist则需要从头或者尾开始遍历,至于从哪里开始遍历,它也是有公式的,就是比较index和(size >> 1)大小,集合右移一位,即接近中间值的值,如果索引比中间值大,则从尾部开始遍历,如果索引比中间值小,则从头部开始遍历。 -* 最后还有一些判断的方法,contain方法调用的是indexof方法,indexof方法里面也是遍历集合加判断元素是否存在的,不存在则返回-1。 反射中,Class.forName和ClassLoader区别   String,Stringbuffer,StringBuilder的区别? @@ -72,42 +10,6 @@ String,Stringbuffer,StringBuilder的区别? 简述NIO的最佳实践,比如netty,mina TreeMap的实现原理 -### JVM相关 - -#### 1、类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序   - * 父类静态变量 --> 父类的静态代码块 --> - 子类静态变量--> 子类的静态代码块 --> - 父类字段 --> 父类代码块1 --> 父类代码块2(2写在1下面) --> 父类构造函数 - 子类字段 --> 子类代码块1 --> 子类代码块2(2写在1下面) --> 子类构造函数 - -#### 2、JVM内存分代   -* 这里的内存是指的是堆内存,而不是非堆内存,堆内存(自己写的代码运行时存储的区域)和非堆内存,也称堆外内存,NIO部分就引用到堆外内存,这部分是直接和系统打交道的,当然native相关的代码也在运行时存储在这里。下面正式谈谈堆内存分代,堆内存分为新生代(三分之一)、老年代(三分之二)、还有永久代( -先不讨论永久代)。新生代里面又分eden区,from区(survivor1),to区(survivor2),对象创建一般先分到Eden区,但大对象直接分到老年代。eden区:from区:to区的空间比例是8:1:1.新生代的垃圾收集算法是复制算法,老年的垃圾收集算法是标记整理算法。当eden区的空间不足以存下新创建的对象时,会进行一次minor GC.存活的对象和survivor1区(from区)的对象复制到to区。然后清空eden区和from区。这时from区和to区进行转换,from变成to,to变成from.而存活的对象年龄加一,大于15岁这个默认值的话,就会认为是长期存活的对象,进到老年代。minor GC回收频率高,速度快。如果to区空间不足,那将触发空间担保机制。将一部分对象存到老年代,如果老年代空间也不足,那就会导致空间担保失败,进行一次Major GC .而进行一次Major GC一般会触发一次Minor GC.触发Major GC的条件还有直接调用System.gc();老年代空间不足等等。Major GC回收频率相对较小,回收时间长。 - -#### 3、Java 8的内存分代改进 -* 永久代 - > 元空间。 -它们之间的区别是:元空间不在虚拟机中,而是使用系统内存。 -使用元空间有什么好处呢?为什么要换成元空间? -原因: -字符串等字面量容易在永久代的运行时常量池中溢出。 -类及方法的信息等在永久代中比较难确定其大小。 -永久代在虚拟机中的回收效率偏低。 - -#### 4、JVM垃圾回收机制,何时触发MinorGC等操作 -* 同2 -#### 5、jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等 -* 同2 -* 大对象:PreTenureSizeThresHold -长期存活的对象:MaxTenuringThresHold -SurvivorRatio - -#### 6、你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1 -* 翻书 - -#### 7、新生代和老生代的内存回收策略 -* 同2 -#### 8、Eden和Survivor的比例分配等   -* 默认8:1:1 深入分析了Classloader,双亲委派机制   JVM的编译优化 From 0ef7d461a38713bcd3f8ccfd77c8f4e9cccfe5fd Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:14:04 +0800 Subject: [PATCH 39/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...30\344\270\216\347\255\224\346\241\210.md" | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index 52a6ed3..7ef9588 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -62,3 +62,41 @@ LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,Ha * 然后说实现方法,arrayList的添加,是先判断容器是否容量足够,初始化是10个,注意的是,容量(capacity)跟集合的元素个数(size)不是一个概念,容量一般是大于元素个数的,如果容量不够,则会自动扩容,也就是大概扩容50%左右,不能说肯定扩容50%,公式是:新容量 = 原容量 + (原容量 >> 1),然后调用工具类Arrays的copyof方法,实现数组之间的复制,而copyof方法调用的是System的copyof方法,System再调就是c/c++库了,我没办法看到System的具体实现方法。其实arrayList的remove方法也很巧妙,也是通过数组之间的复制来实现元素的删除的,具体的我忘了,而linkedList的add方法,只是修改前后两个元素的引用。删除方法同理,也就是改引用,arraylist则可能是移动元素的位置。而set和get方法,arraylist直接通过下标获取或者修改相应的值,linkedlist则需要从头或者尾开始遍历,至于从哪里开始遍历,它也是有公式的,就是比较index和(size >> 1)大小,集合右移一位,即接近中间值的值,如果索引比中间值大,则从尾部开始遍历,如果索引比中间值小,则从头部开始遍历。 * 最后还有一些判断的方法,contain方法调用的是indexof方法,indexof方法里面也是遍历集合加判断元素是否存在的,不存在则返回-1。 +### JVM相关 + +#### 1、类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序   + * 父类静态变量 --> 父类的静态代码块 --> + 子类静态变量--> 子类的静态代码块 --> + 父类字段 --> 父类代码块1 --> 父类代码块2(2写在1下面) --> 父类构造函数 + 子类字段 --> 子类代码块1 --> 子类代码块2(2写在1下面) --> 子类构造函数 + +#### 2、JVM内存分代   +* 这里的内存是指的是堆内存,而不是非堆内存,堆内存(自己写的代码运行时存储的区域)和非堆内存,也称堆外内存,NIO部分就引用到堆外内存,这部分是直接和系统打交道的,当然native相关的代码也在运行时存储在这里。下面正式谈谈堆内存分代,堆内存分为新生代(三分之一)、老年代(三分之二)、还有永久代( +先不讨论永久代)。新生代里面又分eden区,from区(survivor1),to区(survivor2),对象创建一般先分到Eden区,但大对象直接分到老年代。eden区:from区:to区的空间比例是8:1:1.新生代的垃圾收集算法是复制算法,老年的垃圾收集算法是标记整理算法。当eden区的空间不足以存下新创建的对象时,会进行一次minor GC.存活的对象和survivor1区(from区)的对象复制到to区。然后清空eden区和from区。这时from区和to区进行转换,from变成to,to变成from.而存活的对象年龄加一,大于15岁这个默认值的话,就会认为是长期存活的对象,进到老年代。minor GC回收频率高,速度快。如果to区空间不足,那将触发空间担保机制。将一部分对象存到老年代,如果老年代空间也不足,那就会导致空间担保失败,进行一次Major GC .而进行一次Major GC一般会触发一次Minor GC.触发Major GC的条件还有直接调用System.gc();老年代空间不足等等。Major GC回收频率相对较小,回收时间长。 + +#### 3、Java 8的内存分代改进 +* 永久代 - > 元空间。 +它们之间的区别是:元空间不在虚拟机中,而是使用系统内存。 +使用元空间有什么好处呢?为什么要换成元空间? +原因: +字符串等字面量容易在永久代的运行时常量池中溢出。 +类及方法的信息等在永久代中比较难确定其大小。 +永久代在虚拟机中的回收效率偏低。 + +#### 4、JVM垃圾回收机制,何时触发MinorGC等操作 +* 同2 +#### 5、jvm中一次完整的GC流程(从ygc到fgc)是怎样的,重点讲讲对象如何晋升到老年代,几种主要的jvm参数等 +* 同2 +* 大对象:PreTenureSizeThresHold +长期存活的对象:MaxTenuringThresHold +SurvivorRatio + +#### 6、你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms,g1 +* 翻书 + +#### 7、新生代和老生代的内存回收策略 +* 同2 +#### 8、Eden和Survivor的比例分配等   +* 默认8:1:1 + + From 3c6dde5a0a321337ad2252273f258e19edd55b3e Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:14:53 +0800 Subject: [PATCH 40/47] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0714af5..ade6161 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -## java基础 -#### 10、反射的原理,反射创建类实例的三种方式是什么? -#### 11、cloneable接口实现原理,浅拷贝or深拷贝 -#### 12、Java NIO使用 + +10、反射的原理,反射创建类实例的三种方式是什么? +11、cloneable接口实现原理,浅拷贝or深拷贝 +12、Java NIO使用 反射中,Class.forName和ClassLoader区别   String,Stringbuffer,StringBuilder的区别? From be47300eb343d77e8671f76493d29d8b73d30704 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 09:15:17 +0800 Subject: [PATCH 41/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...1\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" | 1 + 1 file changed, 1 insertion(+) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index 7ef9588..a74e181 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -1,3 +1,4 @@ +## java基础 #### 1.Arrays.sort实现原理和Collections.sort实现原理. 答:Arrays.sort()方法,如果数组长度大于等于286且连续升序和连续降序性好的话,就用归并排序,如果大于等于286且连续性不好的话就用双轴快速排序。如果长度小于286且大于等于47的话就用双轴快速排序,如果长度小于47的话就用插入排序.而Collections.sort实际上就是通过toArray方法转换成数组,然后调用TimSort方法,而不会调用LegacyMergeSort方法,即传统归并方法,而TimSort方法的核心思想就是找到数组中的有序子数组,将无序的单独出来排序,最后通过binarysort方法归并合成一个新数组,通过asList转换成集合返回。 #### 2、foreach和while的区别(编译之后)   From 371b447670da6821fbd28da5f0020c22722905d1 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 10:07:22 +0800 Subject: [PATCH 42/47] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ade6161..b530c80 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,9 @@ Http请求get和post的区别以及数据包格式 ## 其他 -maven解决依赖冲突,快照版和发行版的区别 -Linux下IO模型有几种,各自的含义是什么 +#### maven解决依赖冲突,快照版和发行版的区别 +* 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。 +Linux下IO模型有几种,各自的含义是什么   实际场景问题,海量登录日志如何排序和处理SQL操作,主要是索引和聚合函数的应用 实际场景问题解决,典型的TOP K问题 线上bug处理流程 From c0dee22ac13a27b8fe0f3c7d0dc86d4904d5ac6f Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 10:07:52 +0800 Subject: [PATCH 43/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b530c80..ad47d3e 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Http请求get和post的区别以及数据包格式 ## 其他 #### maven解决依赖冲突,快照版和发行版的区别 -* 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。 +*** 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。 Linux下IO模型有几种,各自的含义是什么   实际场景问题,海量登录日志如何排序和处理SQL操作,主要是索引和聚合函数的应用 实际场景问题解决,典型的TOP K问题 From 446edfaf7e5b53c5c18374e8c891e36ab75cc215 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Fri, 6 Apr 2018 10:08:26 +0800 Subject: [PATCH 44/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad47d3e..e9653d5 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Http请求get和post的区别以及数据包格式 ## 其他 #### maven解决依赖冲突,快照版和发行版的区别 -*** 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。 +*** 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。
Linux下IO模型有几种,各自的含义是什么   实际场景问题,海量登录日志如何排序和处理SQL操作,主要是索引和聚合函数的应用 实际场景问题解决,典型的TOP K问题 From 44b7d9427165bef810116a741f4df1d9dac51f14 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Sat, 7 Apr 2018 12:31:32 +0800 Subject: [PATCH 45/47] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index e9653d5..bf64b42 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,6 @@ Http请求get和post的区别以及数据包格式 简述tcp建立连接3次握手,和断开连接4次握手的过程;关闭连接时,出现TIMEWAIT过多是由什么原因引起,是出现在主动断开方还是被动断开方。 ## 其他 - -#### maven解决依赖冲突,快照版和发行版的区别 -*** 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了。
Linux下IO模型有几种,各自的含义是什么   实际场景问题,海量登录日志如何排序和处理SQL操作,主要是索引和聚合函数的应用 实际场景问题解决,典型的TOP K问题 From 9c76519de13f024387bb4d0e8106ef3ffffea64c Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Sat, 7 Apr 2018 12:32:29 +0800 Subject: [PATCH 46/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...27\256\351\242\230\344\270\216\347\255\224\346\241\210.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index a74e181..4272bff 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -100,4 +100,6 @@ SurvivorRatio #### 8、Eden和Survivor的比例分配等   * 默认8:1:1 - +## 其他 +#### maven解决依赖冲突,快照版和发行版的区别 +* 找到对应的依赖,使用exclusion标签排除一下。快照版的版本号带snapshot标记,而稳定版不带,一般生产建议用快照版,这样每次打包编译的时候就可以自动的从镜像服务器获取最新的版本了. From 796642ede0f951ed1c3a6b4a07077b44c4084f65 Mon Sep 17 00:00:00 2001 From: Kevin Chow Date: Mon, 9 Apr 2018 20:06:41 +0800 Subject: [PATCH 47/47] =?UTF-8?q?Update=20=E9=97=AE=E9=A2=98=E4=B8=8E?= =?UTF-8?q?=E7=AD=94=E6=A1=88.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7\256\351\242\230\344\270\216\347\255\224\346\241\210.md" | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" index 4272bff..9525170 100644 --- "a/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" +++ "b/\351\227\256\351\242\230\344\270\216\347\255\224\346\241\210.md" @@ -47,10 +47,7 @@ cglib:cglib动态代理的代理类实现MethodInterceptor接口,重写interce 分析地址:https://coolshell.cn/articles/9606.html
当多条线程同时存取操作hashMap时,就可能会出现infinite loop (死循环),就是当线程之间挂起和执行的链表的指向形成一个环状,就会出现死循环,这个情况出现不是特别明显,是一个隐性的bug,死循环有一个很致命的缺点,就是会让cpu飙升,最后有可能会出现宕机的情况,解决这一问题用hashtable替换或者concurrentHashMap(推荐)替换,或者是使用工具类的包装器,也就是Collections.synchronizedMap()。
HashMap死循环演示
-假如有两个线程P1、P2,以及链表 a=》b=》null
-1、P1先执行,执行完"Entry next = e.next;"代码后发生阻塞,或者其他情况不再执行下去,此时e=a,next=b
-2、而P2已经执行完整段代码,于是当前的新链表newTable[i]为b=》a=》null
-3、P1又继续执行"Entry next = e.next;"之后的代码,则执行完"e=next;"后,newTable[i]为a《=》b,则造成回路,while(e!=null)一直死循环
+两条线程往链表插入数据,如果空间不够=就会扩容,扩容的话,会产生新链表,进行一次指针翻转,另一条线程进来了,就会出现一个链表回路。 #### 9、了解LinkedHashMap的应用吗 LinkedHashMap继承HashMap,也就是在HashMap的基础上进一步封装,HashMap是无序的,LinkedHashMap是有序的,因为在HashMap的基础上添加一个双向链表维护,有序迭代可分为访问顺序和插入顺序迭代,一般默认的是插入顺序迭代元素,即属性accessOrder在LinkedHashMap构造函数里设置为false,访问(调用get或者put方法)顺序的意思是:只要访问过的元素,先删除,即remove,然后add到双向链表的尾部,迭代的时候会从尾部到头部的顺序依次迭代。