今天是女神节哈。先祝广大女神节日快乐,今天面试的是一家大型外包公司某北方。这是卡在两个问题上,而竟然是我比较擅长的领域:数据结构。
问的是ConcurrentHashMap。HashMap的实现原理我肯定知道,但是更深的细节我也没回答出来。面试后的反思时间到。
ConcurrentHashMap实现原理
ConcurrentHashMap源码呢?6000多行,以30行为一页,打印出来,有200页。如果看完,呵呵,这一个类呢,就得看上一个月。那咱这面试还要问其他问题呢?这学得完吗?所以需要有所省略,有所着重地去研究。
ConcurrentHashMap结构
从结构上,ConcurrentHashMap是Node的数组,这个是和HashMap一样的。但是Node在HashMap中只有两个子类。而ConcurrentHashMap的Node有四个子类。那多出来的两个子类是干嘛用的?ForwardingNode是扩容时迁移用的。ReservationNode是compute方法时调用的。在compute方法执行过程中新建一个ReservationNode作为一个锁,并且替换掉原先的Node,是一种并发保护机制。而另外两种子类,TreeBin和TreeNode是用在红黑树的,TreeBin包含了红黑树的根,并且加上了一些用于并发的字段。
ConcurrentHashMap的锁
首先得讲ConcurrentHashMap的读方法是不加锁的,锁都加在写方法上。而锁用两种方式去做的,一种是synchronized关键字,二是CAS。锁的粒度在链表的头节点或者红黑树的TreeBin节点上。相对于JDK1.7,锁粒度更细。
ConcurrentHashMap的扩容
ConcurrentHashMap的扩容在JDK8比JDK7进行了优化。优化点在于协助扩容。扩容是从table的最后一个索引开始的,每次转移开始就放置一个FordwardingNode,这个前面讲过。为了实现协助扩容,有个字段nextTable,这个时候不能用局部变量,因为局部变量其他线程不能访问。
HashMap的扩容因子为什么是0.75
为什么是0.75,是基于空间和时间的考虑,选择了0.75。但更多的会问选择了0.75之后会怎么样。我看了源码才知道在扩容因子为0.75时,HashMap在table的每个索引上出现数据个数的概率服从
λ
=
0.5
\lambda=0.5
λ=0.5的泊松分布。也就是服从以下分布公式:
e
0.5
×
0.
5
k
k
!
=
1
e
×
1
2
k
×
1
k
!
=
1
e
×
2
k
×
k
!
\frac{e^{0.5}\times0.5^k}{k!}=\frac{1}{\sqrt e} \times \frac{1}{2^k}\times \frac{1}{k!}=\frac{1}{\sqrt e\times2^k\times k!}
k!e0.5×0.5k=e1×2k1×k!1=e×2k×k!1
也就是说假如HashMap的table为16个元素,采用0.75,就是放满时12个元素。是这个
3
/
4
3/4
3/4的比例觉得了那么每个桶(也就是数组位置),上出现k个点的概率为:
1
e
×
2
k
×
k
!
\frac{1}{\sqrt e\times2^k\times k!}
e×2k×k!1
在JDK源码中写出了概率:
次数 | 概率 |
---|---|
0 | 0.60653066 |
1 | 0.30326533 |
2 | 0.07581633 |
3 | 0.01263606 |
4 | 0.00157952 |
5 | 0.00015795 |
6 | 0.00001316 |
7 | 0.00000094 |
8 | 0.00000006 |
用数学期望来估算,近似的数学期望是每个位置0.3左右,那么16个位置大约总共是不到5个元素。因为最大12个元素有,所以总共13种情况。
0
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
=
78
0+1+2+3+4+5+6+7+8+9+10+11+12=78
0+1+2+3+4+5+6+7+8+9+10+11+12=78
平均下来
78
13
=
6
\frac{78}{13}=6
1378=6,比5大,但是这是拿16计算的,拿8或者32或其他数计算结果又不一样,所以这是一个总体的结果。而这个总体的结果,需要复杂的统计才能得到符合泊松分布的结论。这个计算过程我就不展开了。