前言
很多同学想知道面试的知识,但是每个点的面太广,如阅读源码太过耗时间,不知道哪些才是重点。所以跟大家分享一下方法,就是像以前英语、语文的阅读理解题,我们可以先看题目,再带着问题去阅读文章,这样效率就会比较快。
所以准备面试也一样,带着问题点,去找答案。
文章形式
《JAVA面试考点导读》系列中,不会详细的叙述每个知识点,我也不重复的造轮子,因为网上有很多文章。在这系列中,我列出每个章节中重要的知识点,每个点的几个概述,然后推荐大家阅读某些文章。
针对阿里P6级别考点,用这种方式来帮大家在浩瀚海洋中画重点。
如:
HashMap
1、数据结构 (常见程度:★★★★ 可以根据常见程度去看或者跳过,最高★★★★★)
…几个关键概述
2、size计算
3、1.7与1.8改进
大家可以私信我,有什么好文章,或者你自己写的好的文章,我会放在此文中,给你们做推广。
下述正文
源码阅读顺序:
https://blog.csdn.net/qq_21033663/article/details/79571506
一、Object对象
1、对象头地址 (常见程度:★★★)
所谓的对象监视器就存储在对象的头部mark_word内存。
帧头,ObjectMonitor对象监视器所有字节包含对象的偏量锁、轻量级锁、重量级、线程ID标记位。
推荐阅读
https://blog.csdn.net/chengzhang1989/article/details/78703818
2、 锁升级 (常见程度:★★★)
进入锁流程
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
锁升级
描述偏斜锁 轻量级 重量级,各个锁之间如何升级变化
- synchronized锁对象时也是对象锁,也是使用Object的monitor形式。
- 一般情况下没有竞争,当没有竞争出现时,默认会使用偏斜锁,就用标志,set线程id号,用于表示可重入。
- 后续有标志了,又有竞争就会cas,则转轻量级
- cas不成功再转重量级,如果当前的锁正在膨胀中(轻量级锁膨胀为重量级锁),重新获取mark_word,如有才升级为重量级锁。
推荐阅读
https://cloud.tencent.com/developer/article/1414969
https://www.cnblogs.com/jxxblogs/p/11890563.html
利用cas变更线程锁的状态
https://www.cnblogs.com/yinbiao/p/10596463.html
COMPARE 偏移值(内存地址),当前值、预期值
3、 wait(), notify(), notifyAll(), wait(timeout) (常见程度:★)
wait
总结下Object.wait()实现原理:
- 首先能够调用wait方法,说明已经持有锁(或者说进入了synchronized代码块),而wait依赖于对象监视器objectmonitor的实现,所以首先得要获取这个monitor
- monitor的指针存放在对象的头部,如果对象的头部没有,首先尝试从当前线程维护的omFreelist链表中分配,如果没有,从全局的gFreelist中分配,如果还没分配到,通过new的方式分配一个128大小的ObjectMonitor数组,放入到当前线程的omFreelist,以及全局的gFreelist,同时设置必要的信息
- monitor分配好以后,进入wait,首先将当前线程封装成一个ObjectWaiter放入到objectMonitor的waitset队列中,然后退出对象监视器,挂起当前线程!
notify
- notify时只是把notify标志职位1,当前线程加入到enter(synchronized)队列中,准备重新获取锁。既没有unpark挂起的线程,也没有调用ObjectMonitor::exit方法释放锁!!!
- 主要区别就是wait放入waitset然后会park
- notify是放入enter但是调用unpark 唤醒
4、锁粗化(常见程度:★)
for循环里面synchronized,若对象没有逃逸到方法外,synchronized会编译时改为for外面
5、锁消除(常见程度:★)
看方法内某个变量是否不会被其他引用,或者逃逸到方法外,如果没有逃逸到方法外,就去除synchronized
6、CAS引起的问题aba (常见程度:★★)
有三个线程
线程1读出A
线程2把A改为B
线程3把B改为A,线程1就未感知。
推荐阅读
https://blog.csdn.net/qq_36951116/article/details/86502892
7、 hashCode(), equals() (常见程度:★★★)
hashcode一样,内存地址不一定一样
推荐阅读
https://blog.csdn.net/weixin_39532479/article/details/79814516
为什么都要重写
推荐阅读
https://blog.csdn.net/xl_1803/article/details/80445481
默认使用hashcode,获取map,相同信息但是获取不了,因为人为比较是根据成员属性,而不是内存对象地址
推荐阅读
https://baijiahao.baidu.com/s?id=1620197502415557866&wfr=spider&for=pc
如果equals而不重写hashcode,那么Student类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然此时s1!=s2,故两者的hashcode不一定相等。
主要是因为多数比较equal时,如hashmap,先比较hashCode
那为什么不直接用equal比较,因为Object的equal是比较对象地址,hashCode比较快
业务比较都是比较信息,而不是比较地址。
hashcode默认是内存地址
https://www.jianshu.com/p/db67af6182d7
8、 clone()
深拷贝,浅拷贝
复制时,要看成员是引用对象,还是值(基本类型)。
复制引用对象时会修改原来的。
推荐阅读
https://www.cnblogs.com/nickhan/p/8569329.html
二、String (常见程度:★)
final
String 为final修饰,不能重写、继承
数据结构
- char[] value
- int hash
- equals(), startWith(), endWith(), replace
三、AbstractStringBuilder
- 数组 char[] value
- int count
- 扩容:2倍,内存不够则取所需最小。 将扩大的部分全部用’\0’
扩容或add前先确认长度,后变长
int newCapacity = (value.length ) * 2;
append方法会先判断是否需要扩容, 先扩容,后插入
进过我查证源码发现,它Append()在执行的时候会判断添加的元素之前会判断现在的容量是否已满,已满的话会进行一次双倍扩容,当然不管你添加多少元素它只扩容一次,在添加完成之后,容量会和实际数量进行判断,实际数量大于容量容量就等于实际容量,当然前提是你没有设置StringBuilder的最大容量。
推荐阅读
https://blog.csdn.net/qq_41600074/article/details/81976745
利用System.arraycopy
insert在某个位置插入str:System.arraycopy(value, index, value, index + len, count - index);
#四、StringBuffer
- 继承AbstractStringBuilder
- synchronized方法保证线程安全
- char[] toStringCache
5、StringBuilder 继承AbstractStringBuilder
五、Thread (常见程度:★)
https://blog.csdn.net/linxdcn/article/details/72819817?locationNum=13&fps=1
1、四个状态 (常见程度:★★★)
- 就是 生命周期
NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
- BLOCKED 和 WATING 的区别
一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify,
WATING 等待唤醒,BLOCKED等待进入
2、流程、主要方法
推荐阅读
https://blog.csdn.net/caoxiaohong1005/article/details/80312396
start()
// 启动一个线程 public synchronized void start() { // 线程不能重复start if (threadStatus != 0)
join()
调用wai(0)
###不用stop的原因
https://www.cnblogs.com/DreamDrive/p/5623804.html
同步锁,标志位
synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { // 只是设置了中断标志位
interrupt() (常见程度:★★)
sleep也是中断,所以interrupt中断后,再中断就会抛异常,这样线程内就可以人为控制退出
https://blog.csdn.net/zhangliangzi/article/details/52485319
https://blog.csdn.net/canot/article/details/51087772
3、退出线程时注意
JVM退出时Daemon线程中的finally块中的代码不一定会执行。因此不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑
六、ThreadLocal(常见程度:★★★)
https://www.cnblogs.com/dolphin0520/p/3920407.html
https://maimai.cn/article/detail?fid=1281999455&efid=YiUo6zi9_I_9HIrGkHTpuw&from=timeline
-
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。
可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
entry以ThreadLocal为key,其实是为了构造entry的父类,以Object为value -
注意没有先set,直接get的话,运行时会报空指针异常。
重写了initialValue方法 就可以直接不用先set而直接调用get了。
3.ThreadLocal弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,
Entry将全部被GC回收。但如果是线程对象不被回收的情况,比如使用线程池,线程结束是不会被销毁的,就可能出现真正意义上的内存泄漏。
jdbc深入理解connection与threadlocal
https://www.cnblogs.com/panxuejun/p/5920845.html
七、ArrayList (常见程度:★★)
- Object[] elementData
- int size
- 默认大小10
- 扩容:*1.5,不够取所需最小
但ArrayList非线程安全,达到数组长度时每次扩大50%:用>>1的方式来加原来的长度
所以>>1等于乘以0.5
ArrayList扩容
默认长度10
https://blog.csdn.net/zymx14/article/details/78324464
add时限保证容量,检查好容量后,add对象进去
size表示使用多少个元素,add每次+1,到10就扩容
https://www.cnblogs.com/woshimrf/p/arraylist.html
八、LinkedList (常见程度:★★)
- Node {E item, Node prev, Node next}
- int size
- Node first
- Node last
- linkFirst(), linkLast(), linkBefore(), unLinkFirst(), unLinkLast(), unLink(), indexOf()
LinkedList和ArrayList的区别(常见程度:★★★★)
插入速度优先linkedList
查询速度优先ArrayList
九、HashMap (常见程度:★★★★★)
key可以为null
- Node{int hash, K key, V value, Node next}
- 默认容量16,负载因子0.75f
https://blog.csdn.net/tingting256/article/details/52475422
1、扩容
首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。看上去很简单,其实比较有玄机。比如数组的长度是2的4次方,那么hashcode就会和2的4次方-1
为什么扩容后?用同样的hashcode能够找到新位置而不是原来位置,因为获取位置的时候是根据数组长度来进行计算
- int size, modCount, threshold, float loadFactor
- Node[] table
- Set entrySet
- put():根据key算hash,根据容量和hash算index,table[index]没有直接添加到数组中,table[index]有,需要判断hashCode 和equal,若index位置同一个key则更新,否则遍历next是否有,有则更新,无则新增链表节点,最后根据thread与size判断是否扩容。注:扩容时容量翻倍,重新算hash复制到新数组
put的时候创建数组
https://blog.csdn.net/github_30602055/article/details/79208576
7)get()类似
注:先比较hash,若相等在比较equals
##2、 先判断hashcode相同就进入同个key,如果equal不相同就进入链表,链表太长才会转换tree
https://www.cnblogs.com/wangflower/p/12235595.html
##3、 rehash
多线程下,同时扩容rehash时发生
https://blog.csdn.net/hll174/article/details/50915346
https://www.cnblogs.com/dongguacai/p/5599100.html
hashCode相同但是值不相同的案例
String str1 = “通话”; String str2 = “重地”; System. out. println(String. format(“str1:%d | str2:%d”, str1. hashCode(),str2. hashCode())); System. out. println(str1. equals(str2));
##4、 1.7与1.8有什么不同 (常见程度:★★★★★)
推荐阅读
https://blog.csdn.net/qq_36520235/article/details/82417949
主要是头插入尾巴插入,解决两个线程同时并行,并且rehash的时候出现。但是这个不是线程安全的变量,不应该去考虑多线程的问题。
和扩容后新位置,原位置+扩展容量的大小。
注意:11111,2的指数
5、解决冲突方式
https://blog.csdn.net/weixin_41163113/article/details/84974414
hash算法、位或运算
链表、数组、1.8才有红黑树、
hash值一样,但是equal不相同
https://blog.csdn.net/u010775025/article/details/79474023
避免rehash优化:2的指数倍,恰好够大
6、HashMap为啥8个变链表?
泊松分布,转换概率
链表查找速度和数组速度在8开始有差异
6个才还原,主要为了减少变换频率
##7、 HashMap的size为啥2的倍数? (常见程度:★★★★★)
因hash是对0xFFFFFFFFF进行或运算,这样会对空间利用高,提高防止重复key
九、LinkHashMap
https://blog.csdn.net/qq_16691531/article/details/79965677
afterNodeAccess处理顺序:插入到链表最后方法,hashmap有使用这个方法,但是没有逻辑
removeEldestEntry 处理删除元素条件:为了保持队列中元素总数
- Entry{HashMap.Node, Entry before, after}
- Entry head, tail
- 重写newNode()添加节点时,除像HashMap中添加外,保存before、after信息
lru
lru重写删除方法,到达5就删除,保持5个。
十、hashmap加分项
1、为什么hashmap不用val
数据结构
AVL树就是平衡树
B树是平衡树
B+树是多路平衡树、数据在叶子节点链表上
红黑树是二叉搜索树
hashmap的TreeMap是红黑树
原因
红黑树为了插入时减少旋转放弃平衡,平衡二叉树为了平衡插入时增加旋转次数
https://blog.csdn.net/theshowlen/article/details/92184314
https://zhidao.baidu.com/question/90010640.html
红黑树不追求"完全平衡",
而AVL是严格平衡树(B树),因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。
红黑树最大路劲高不超过最短路径的2倍。
先考虑查找速度,后再考虑变换,红黑树时个不错的折中选择
红黑树在数据量大就会高瘦,很深,深的时候就是数据量大的时候,查找速度慢。
如果数据量多才会用avl平衡树=B树,B树是多叉的,所以数据量大会横向扩展,深度小,查找速度相对快。部分平衡
所以数据库用mysql用B+树多平衡树。
HashMap少量数据,少量冲突,用红黑。量少,占用空间少,插入速度快,查找速度不平均
2、为什么hashmap中的链表需要转成红黑树
https://www.cnblogs.com/rgever/p/9643872.html
为了查找速度,但是影响插入速度
红黑树logn 输入8 =3
链表n/2 输入8=4
https://juejin.im/entry/5d7376cf51882535841708bc
3、为什么16默认长度
如果太小,4或者8,扩容比较频繁;如果太大,32或者64甚至太大,又占用内存空间
4、为什么2的倍数
因此,默认初始化大定义为2的幂,就是为了使用更高效的与运算。
5、默认加载因子为什么是0.75
如果是0.5,就是说哈希表填到一半就开始扩容了,这样会导致扩容频繁,并且空间利用率比较低。 如果是1,就是说哈希表完全填满才开始扩容,这样虽然空间利用提高了,但是哈希冲突机会却大了。
6、最大容量为什么是1 << 30
int占四个字节,一个字节占8位,所以是32位整型,也就是说最多32位
System.out.println(1<<31);有一个是 负数,所以最大31位,所以只能左移30
7、树转链表
链表树化阀值是8,那么树还原为链表为什么是6而不是7呢?
这是为了防止链表和树之间频繁的转换
#十一、 TreeMap(常见程度:★)
1)红黑树,即自平衡二叉查找树,时间复杂度O(logn)
2)Entry{K k, V v, Entry parent, left, right, boolean color}
3)Entry root,int size, int modeCount
https://www.cnblogs.com/williamjie/p/9099130.html
##1、结构
https://blog.csdn.net/f641385712/article/details/81147941
##2、实现原理
https://blog.csdn.net/itmyhome1990/article/details/76213883
比较大小插入
平衡二叉查找树
TreeMap数据内部
https://blog.csdn.net/Hubery_James/article/details/80564907
有颜色
左右
循环比较然后插入
TreeMap元素必须实现Comparable接口
https://www.cnblogs.com/Booker808-java/p/9124065.html
比较和重复通过compareTo
看看HashMap源码
##3、与HashMap 效率对比
https://ytrgmj.iteye.com/blog/1478063
HashMap续写效率高,排序TreeMap高
https://blog.csdn.net/m0_37797282/article/details/80280544
有序用TreeMap
https://www.cnblogs.com/williamjie/p/9099130.html
#十二、Hashtable
- 结构实现与HashMap基本一致
- 通过synchronized方法保证线程安全
十三、HashSet
委托给HashMap,其Value是同一个默认对象
十四、LinkedHashSet
LinkedHashSet继承HashSet:不知道如何实现的顺序?
LinkList和LinkSet都是双向链表
十五、ConcurrentHashMap (常见程度:★★★★★)
1、 JDK1.7及以前:
https://blog.csdn.net/qq_33589510/article/details/79962152
其中segments是Segment的原生数组,此数组的长度可以在ConcurrentHashMap的构造函数中使用并发度参数指定,其默认值为DEFAULT_CONCURRENCY_LEVEL=16
egments数组的长度ssize是通过concurrencyLevel计算得出的
为了能通过按位与的散列算法来定位segments数组的索引,必须保证segments数组的长度是2的N次方,所以必须计算出一个大于或等于concurrencyLevel的最小的2的N次方值来作为segments数组的长度
a、Segment[] ,HashEntry[] , HashEntry{hash, k, v, next}
b、根据key算hash,根据hash和Segment的大小算位置,每个segment拥有一个自己的HashEntry[]
c、get():不加锁,volatile类型 的HashEntry以后叫node
d、put(): 对相应segment加锁
1、判断是否需要对Segment里的HashEntry数组进行扩容
2、Unsafe的getObjectVolatile来读取segments中的元素
3、ConcurrentHashMap使用分段锁Segment来保护不同段的数据,那么在插入和获取元素时,必须先通过散列算法定位到Segment.
4、HashEntry使用UNSAFE.putOrderedObject来设置它的next成员变量,这样既可以提高性能,又能保持并发可见性。同时,entryAt方法和setEntryAt方法也使用了UNSAFE.getObjectVolatile和UNSAFE.putOrderedObject来读取和写入指定索引的HashEntry。
e、size():各HashEntry[] 之和,先不加锁算两遍,若一致则返回,若不一致则加锁重新计算
modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化.
Segment继承reeteenLock
2. JDK1.8
关键 桶,cas插入,树,size
http://www.importnew.com/23610.html
https://www.cnblogs.com/aspirant/p/8623864.html
https://segmentfault.com/a/1190000016066203
数据结构
a、Node{hash, key, value, next}
b、Node[] table
流程
只有在执行第一次put
方法时才会调用initTable()
初始化Node
数组
c、大多数操作类似于HashMap,不同CAS方式设置,根据key算hash,在根据hash和容量算index,对table[index]加锁,从而达到更大的并发量
d、get(): 同HashMap
e、put(): 对table[index]加锁
如果相应位置的Node
还未初始化,则通过CAS插入相应的数据;
果相应位置的Node
不为空
则对该节点加synchronized
锁,如果该节点的hash
不小于0,则遍历链表更新节点或插入新节点
如果该节点是TreeBin
类型的节点,说明是红黑树结构,则通过putTreeVal
方法往红
果binCount
不为0,说明put
操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin
方法转化为红黑树
只对高16位进行hash与计算
hash算法
(h = key.hashCode()) ^ (h >>> 16);
hashcode 异或,右移
size:
1、两条线程add数据后,cas修改baseCount,只有一条成功,失败的那一条会使
CounterCell记录元素的变化。
2、通过CAS
设置cellsBusy字段,只有设置成功的线程才能初始化CounterCell
数组
3、如果通过CAS
设置cellsBusy字段失败的话,则继续尝试通过CAS
修改baseCount
字段,如果修改baseCount
字段成功的话,就退出循环,否则继续循环插入CounterCell
对象;
4、所以他统计size的时候,需要统计baseCount和 CounterCell数组中元素的个数
key能为null
https://blog.csdn.net/qingmengwuhen1/article/details/70060838
为什么没有ConcurrentArrayList
https://www.sypopo.com/post/qbQl9a1KrZ/
ConcurrentHashMap是因为可以多个位置不同情况插入
但是array只能队列后面插入,所以锁的时候就会会变成vector或者copyOnwriteArray
主要判断方法contain 、 remove
判断方法contain 锁为了少判断contain某个东西
ArrayList有remove吗 有 锁为了判断size 有
十六、 Collections
https://www.jianshu.com/p/51ce612db017
binarySearch方法
该方法会根据条件的不同调用两个私有的方法来进行查找。如果List支持随机访问,并且小于二分查找的阈值5000,则调用indexedBinarySearch,否则,调用iteratorBinarySearch,借助迭代器来进行访问。
shuffle方法
shuffle翻译过来是重新洗牌的意思,该方法是将list原有数据打乱生成一个新的乱序列表。通俗点来说,旧相当于重新洗牌,打乱原来的顺序。还有一点,shuffle方法再生成乱序列表的时候,所有元素发生交换的可能性是近似相等的。
十七 、Arrays
#十八 、SubList
#十九、 同步容器
https://www.cnblogs.com/dolphin0520/p/3933404.html
在Java中,同步容器主要包括2类:
1)Vector、Stack、HashTable
2)Collections类中提供的静态工厂方法创建的类
Collections.synchronizedCollection 同步容器
#二十、 Vetor同步容器
由于Vector中的add方法和get方法都进行了同步,因此,在有多个线程进行访问时,如果多个线程都只是进行读取操作,那么每个时刻就只能有一个线程进行读取,其他线程便只能等待,这些线程必须竞争同一把锁。
因为可以remove,并且不是同步的,所以会导致读取的时候,同时被remove了
Vector实现了List接口,Vector实际上就是一个数组
https://www.cnblogs.com/zedosu/p/6662231.html
capacity是Vector的默认容量大小。当由于增加数据导致容量增加时,每次容量会增加一倍。=
只有remove不是synchronized,所以删除数据不同步。
二十一、并发同步容器
https://www.cnblogs.com/shijiaqi1066/p/3412275.html
CopyOnWriteArrayList 与 CopyOnWriteArraySet
就是写的时候做锁操作,读的时候不加锁。
写的时候复制一份集合,先修改,后赋值给旧引用,为了读的时候读到原来的。
CopyOnWriteArrayList
copyonwriteArrayList
https://blog.csdn.net/weixin_34416649/article/details/92415643
靠reentranLock和 复制数组
使用
https://www.cnblogs.com/geekdc/p/10289192.html
解决并发写的问题,用reentranLock方式
底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
https://blog.csdn.net/hua631150873/article/details/51306021
线程与同步并发容器面试题
二十二、迭代器(常见程度:★★)
用集合的删除会有问题,因为modCount(记录了对集合修改的次数)的值不等于expectedModCount(通过迭代器对集合修改的次数),所以在next或者remove的时候checkForComodification检查就报错
foreach
所以在判断hasnext的时候,当前游标和size,集合本身的remove方法只有删除倒数第二个的时候不会报错
https://www.cnblogs.com/liyong888/p/7799272.html
https://www.cnblogs.com/YYCat/p/4675084.html
迭代器Iterator不能remove后又remove,要么只remove一次,要么remove前执行next。
为了更改lastRet
next的时候就已经指向下一个位置
https://blog.csdn.net/qq_34115899/article/details/81978660
二十二、HashCode(常见程度:★★)
代表内存空间地址,原本默认根据内存地址生成
也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashcode值相等,则equals方法得到的结果未知。
https://blog.csdn.net/a745233700/article/details/83186808
为什么重写equal就要重写hashCode
https://www.cnblogs.com/skywang12345/p/3324958.html
https://www.cnblogs.com/ouym/p/8963219.html
hash值相同,但是比较对象引用地址不同
作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals,那么 hashCode一定相同规则。
为了两个方法中的参数要一致,为了保持比较的内容不是地址。
为什么 HashMap 常用 String 对象作 key
https://blog.csdn.net/codejas/article/details/78837830
其实主要就是String的hash 和equal比较好算,如果用对象就要重写两个方法
二十三、automic long与Longadder
https://blog.csdn.net/f641385712/article/details/84934085
累加循环试错-》各自增加的数量sum
weakHashMap
https://www.jianshu.com/p/6598f065edc0
https://blog.csdn.net/qiuhao9527/article/details/80775524
https://www.jianshu.com/p/6904d632549f
https://www.cnblogs.com/micrari/p/8443170.html
实验
https://blog.csdn.net/zjq_1314520/article/details/78565981
key没有被引用时,gc时会有另外一个线程就放入队列中
下次操作时,就会删除。
欢迎关注公众号,文章更快一步
我的公众号 :地藏思维
掘金:地藏Kelvin
简书:地藏Kelvin