ConcurrentHashMap1,2024最新Java面试题目解答

if (as == null || (m = as.length - 1) < 0 ||

(a = as[ThreadLocalRandom.getProbe() & m]) == null ||

!(uncontended =

U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

//以上任何一种情况成立,都会进入该方法,传入的uncontended是false

fullAddCount(x, uncontended);

return;

}

if (check <= 1)

return;

//计算元素个数

s = sumCount(); //我们在这里得到元素的个数

}

//接着判断是否需要扩容

if (check >= 0) {

Node<K,V>[] tab, nt; int n, sc;

//当元素个数达到扩容阈值

//并且数组不为空

//并且数组长度小于限定的最大值

//满足以上所有条件,执行扩容

while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&

(n = tab.length) < MAXIMUM_CAPACITY) {

//这个是一个很大的正数

int rs = resizeStamp(n);

//sc小于0,说明有线程正在扩容,那么会协助扩容

if (sc < 0) {

//扩容结束或者扩容线程数达到最大值或者扩容后的数组为null或者没有更多的桶位需要转移,结束操作

if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||

transferIndex <= 0)

break;

//扩容线程加1,成功后,进行协助扩容操作

if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))

//协助扩容,newTable不为null

transfer(tab, nt);

}

//没有其他线程在进行扩容,达到扩容阈值后,给sizeCtl赋了一个很大的负数

//1+1=2 --》 代表此时有一个线程在扩容

//rs << RESIZE_STAMP_SHIFT)是一个很大的负数

else if (U.compareAndSwapInt(this, SIZECTL, sc,

(rs << RESIZE_STAMP_SHIFT) + 2))

//扩容,newTable为null

transfer(tab, null);

s = sumCount();

}

}

}

2、fullAddCount方法

需要添加的个数分别累加到baseCount 上、或者累加到其他CountCell数组中的每个对象中的value属性上

private final void fullAddCount(long x, boolean wasUncontended) {

int h;

if ((h = ThreadLocalRandom.getProbe()) == 0) { //获取当前线程的hash值(就是确定加到具体哪一个累加单元!)

ThreadLocalRandom.localInit();

h = ThreadLocalRandom.getProbe();

wasUncontended = true;

}

//注意:我们的辅助数组CountCell中保存的是CountCell对象,而我们value是这个对象的有一个属性,所以找到位置,还需要判断是否需要创建对象,如果需要就创建,如果不需要就直接在其value属性的基础上进行添加!

//标识是否有冲突,如果最后一个桶不是null,那么为true

boolean collide = false; // True if last slot nonempty

for (;😉 {

CounterCell[] as; CounterCell a; int n; long v;

//数组不为空,优先对数组中CouterCell的value累加

if ((as = counterCells) != null && (n = as.length) > 0) {

//线程对应的桶位为null

if ((a = as[(n - 1) & h]) == null) {

if (cellsBusy == 0) { // Try to attach new Cell

//创建CounterCell对象

CounterCell r = new CounterCell(x); // Optimistic create

//利用CAS修改cellBusy状态为1,成功则将刚才创建的CounterCell对象放入数组中

if (cellsBusy == 0 &&

U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {

boolean created = false;

try { // Recheck under lock

CounterCell[] rs; int m, j;

//桶位为空, 将CounterCell对象放入数组

if ((rs = counterCells) != null &&

(m = rs.length) > 0 &&

rs[j = (m - 1) & h] == null) {

rs[j] = r;

//表示放入成功

created = true;

}

} finally {

cellsBusy = 0;

}

if (created) //成功退出循环

break;

//桶位已经被别的线程放置了已给CounterCell对象,继续循环

continue; // Slot is now non-empty

}

}

collide = false;

}

//桶位不为空,重新计算线程hash值,然后继续循环

else if (!wasUncontended) // CAS already known to fail

wasUncontended = true; // Continue after rehash

//重新计算了hash值后,对应的桶位依然不为空,对value累加

//成功则结束循环

//失败则继续下面判断

else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))

break;

//数组被别的线程改变了,或者数组长度超过了可用cpu大小,重新计算线程hash值,否则继续下一个判断

else if (counterCells != as || n >= NCPU)

collide = false; // At max size or stale

//当没有冲突,修改为有冲突,并重新计算线程hash,继续循环

else if (!collide)

collide = true;

//如果CounterCell的数组长度没有超过cpu核数,对数组进行两倍扩容

//并继续循环

else if (cellsBusy == 0 &&

U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {

try {

if (counterCells == as) {// Expand table unless stale

CounterCell[] rs = new CounterCell[n << 1];

for (int i = 0; i < n; ++i)

rs[i] = as[i];

counterCells = rs;

}

} finally {

cellsBusy = 0;

}

collide = false;

continue; // Retry with expanded table

}

h = ThreadLocalRandom.advanceProbe(h);

}

//CounterCell数组为空,并且没有线程在创建数组,修改标记,并创建数组

else if (cellsBusy == 0 && counterCells == as &&

U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {

boolean init = false;

try { // Initialize table

if (counterCells == as) {

CounterCell[] rs = new CounterCell[2];

rs[h & 1] = new CounterCell(x);

counterCells = rs;

init = true;

}

} finally {

cellsBusy = 0;

}

if (init)

break;

}

//数组为空,并且有别的线程在创建数组,那么尝试对baseCount做累加,成功就退出循环,失败就继续循环

else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))

break; // Fall back on using base

}

}

3、sumCount方法

最后会调用这个方法会中所有累加单元和baseCount的值!

final long sumCount() {

CounterCell[] as = counterCells; CounterCell a;

//获取baseCount的值

long sum = baseCount;

if (as != null) {

//遍历CounterCell数组,累加每一个CounterCell的value值

for (int i = 0; i < as.length; ++i) {

if ((a = as[i]) != null)

sum += a.value;

}

}

return sum;

}

4、size方法

返回的是我们统计好的sum值

public int size() {

long n = sumCount();

return ((n < 0L) ? 0 :

(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :

(int)n);

}

其实size()函数返回的总数可能并不是百分百精确的,试想如果前一个遍历过的CounterCell又进行了更新会怎么样?尽管只是一个估算值,但在大多数场景下都还能接受,而且性能上是要比Java 7好上太多了。

四、扩容


ConcurrentHashMap触发扩容的时机与HashMap类似,要么是在将链表转换成红黑树时判断table数组的长度是否小于阈值(64),如果小于就进行扩容而不是树化,要么就是在添加元素的时候,判断当前Entry数量是否超过阈值,如果超过就进行扩容。

1、普通扩容机制

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {

int n = tab.length, stride;

//如果是多cpu,那么每个线程划分任务,最小任务量是16个桶位的迁移

if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)

stride = MIN_TRANSFER_STRIDE; // subdivide range

//如果是扩容线程,此时新数组为null

if (nextTab == null) { // initiating

try {

@SuppressWarnings(“unchecked”)

//两倍扩容创建新数组

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];

nextTab = nt;

} catch (Throwable ex) { // try to cope with OOME

sizeCtl = Integer.MAX_VALUE;

return;

}

nextTable = nextTab;

//记录线程开始迁移的桶位,从后往前迁移

transferIndex = n;

}

//记录新数组的末尾

int nextn = nextTab.length;

//已经迁移的桶位,会用这个节点占位(这个节点的hash值为-1–MOVED)

ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);

boolean advance = true;

boolean finishing = false; // to ensure sweep before committing nextTab

for (int i = 0, bound = 0;😉 {

Node<K,V> f; int fh;

while (advance) {

int nextIndex, nextBound;

//i记录当前正在迁移桶位的索引值

//bound记录下一次任务迁移的开始桶位

//–i >= bound 成立表示当前线程分配的迁移任务还没有完成

if (–i >= bound || finishing)

advance = false;

//没有元素需要迁移 – 后续会去将扩容线程数减1,并判断扩容是否完成

else if ((nextIndex = transferIndex) <= 0) {

i = -1;

advance = false;

}

//计算下一次任务迁移的开始桶位,并将这个值赋值给transferIndex

else if (U.compareAndSwapInt

(this, TRANSFERINDEX, nextIndex,

nextBound = (nextIndex > stride ?

nextIndex - stride : 0))) {

bound = nextBound;

i = nextIndex - 1;

advance = false;

}

}

//如果没有更多的需要迁移的桶位,就进入该if

if (i < 0 || i >= n || i + n >= nextn) {

int sc;

//扩容结束后,保存新数组,并重新计算扩容阈值,赋值给sizeCtl

if (finishing) {

nextTable = null;

table = nextTab;

sizeCtl = (n << 1) - (n >>> 1);

return;

}

//扩容任务线程数减1

if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {

//判断当前所有扩容任务线程是否都执行完成

if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)

return;

//所有扩容线程都执行完,标识结束

finishing = advance = true;

i = n; // recheck before commit

}

}

//当前迁移的桶位没有元素,直接在该位置添加一个fwd节点

else if ((f = tabAt(tab, i)) == null)

advance = casTabAt(tab, i, null, fwd);

//当前节点已经被迁移

else if ((fh = f.hash) == MOVED)

advance = true; // already processed

else {

//当前节点需要迁移,加锁迁移,保证多线程安全

//此处迁移逻辑和jdk7的ConcurrentHashMap相同,不再赘述

synchronized (f) {

if (tabAt(tab, i) == f) {

Node<K,V> ln, hn;

if (fh >= 0) {

int runBit = fh & n;

Node<K,V> lastRun = f;

for (Node<K,V> p = f.next; p != null; p = p.next) {

int b = p.hash & n;

if (b != runBit) {

runBit = b;

lastRun = p;

}

}

if (runBit == 0) {

ln = lastRun;

hn = null;

}

else {

hn = lastRun;

ln = null;

}

for (Node<K,V> p = f; p != lastRun; p = p.next) {

int ph = p.hash; K pk = p.key; V pv = p.val;

if ((ph & n) == 0)

ln = new Node<K,V>(ph, pk, pv, ln);

else

hn = new Node<K,V>(ph, pk, pv, hn);

}

setTabAt(nextTab, i, ln);

setTabAt(nextTab, i + n, hn);

setTabAt(tab, i, fwd);

advance = true;

}

else if (f instanceof TreeBin) {

TreeBin<K,V> t = (TreeBin<K,V>)f;

TreeNode<K,V> lo = null, loTail = null;

TreeNode<K,V> hi = null, hiTail = null;

int lc = 0, hc = 0;

for (Node<K,V> e = t.first; e != null; e = e.next) {

int h = e.hash;

TreeNode<K,V> p = new TreeNode<K,V>

(h, e.key, e.val, null, null);

if ((h & n) == 0) {

if ((p.prev = loTail) == null)

lo = p;

else

loTail.next = p;

loTail = p;

++lc;

}

else {

if ((p.prev = hiTail) == null)

hi = p;

else

hiTail.next = p;

hiTail = p;

++hc;

}

}

ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :

(hc != 0) ? new TreeBin<K,V>(lo) : t;

hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :

(lc != 0) ? new TreeBin<K,V>(hi) : t;

setTabAt(nextTab, i, ln);

setTabAt(nextTab, i + n, hn);

setTabAt(tab, i, fwd);

advance = true;

}

}

}

}

}

}

图解

2、多线程协助扩容

多线程协助扩容的操作会在两个地方被触发:

① 当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素

② 当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizeCtl的值小于0,并且新数组不为空,这个时候,会去协助扩容

1.1、元素未添加,先协助扩容,扩容完后再添加元素

final V putVal(K key, V value, boolean onlyIfAbsent) {

if (key == null || value == null) throw new NullPointerException();

int hash = spread(key.hashCode());

int binCount = 0;

for (Node<K,V>[] tab = table;😉 {

Node<K,V> f; int n, i, fh;

if (tab == null || (n = tab.length) == 0)

tab = initTable();

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

if (casTabAt(tab, i, null,

new Node<K,V>(hash, key, value, null)))

break; // no lock when adding to empty bin

}

//发现此处为fwd节点,协助扩容,扩容结束后,再循环回来添加元素

else if ((fh = f.hash) == MOVED)

tab = helpTransfer(tab, f);

//省略代码

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {

Node<K,V>[] nextTab; int sc;

if (tab != null && (f instanceof ForwardingNode) &&

(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {

int rs = resizeStamp(tab.length);

while (nextTab == nextTable && table == tab &&

(sc = sizeCtl) < 0) {

if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

sc == rs + MAX_RESIZERS || transferIndex <= 0)

break;

if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {

//扩容,传递一个不是null的nextTab

transfer(tab, nextTab);

break;

}

}

return nextTab;

}

return table;

}

1.2、先添加元素,再协助扩容

private final void addCount(long x, int check) {

//省略代码

if (check >= 0) {

Node<K,V>[] tab, nt; int n, sc;

//元素个数达到扩容阈值

while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&

(n = tab.length) < MAXIMUM_CAPACITY) {

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后的内容

在开头跟大家分享的时候我就说,面试我是没有做好准备的,全靠平时的积累,确实有点临时抱佛脚了,以至于我自己还是挺懊恼的。(准备好了或许可以拿个40k,没做准备只有30k+,你们懂那种感觉吗)

如何准备面试?

1、前期铺垫(技术沉积)

程序员面试其实是对于技术的一次摸底考试,你的技术牛逼,那你就是大爷。大厂对于技术的要求主要体现在:基础,原理,深入研究源码,广度,实战五个方面,也只有将原理理论结合实战才能把技术点吃透。

下面是我会看的一些资料笔记,希望能帮助大家由浅入深,由点到面的学习Java,应对大厂面试官的灵魂追问

这部分内容过多,小编只贴出部分内容展示给大家了,见谅见谅!

  • Java程序员必看《Java开发核心笔记(华山版)》

  • Redis学习笔记

  • Java并发编程学习笔记

四部分,详细拆分并发编程——并发编程+模式篇+应用篇+原理篇

  • Java程序员必看书籍《深入理解 ava虚拟机第3版》(pdf版)

  • 大厂面试必问——数据结构与算法汇集笔记

其他像Spring,SpringBoot,SpringCloud,SpringCloudAlibaba,Dubbo,Zookeeper,Kafka,RocketMQ,RabbitMQ,Netty,MySQL,Docker,K8s等等我都整理好,这里就不一一展示了。

2、狂刷面试题

技术主要是体现在平时的积累实用,面试前准备两个月的时间再好好复习一遍,紧接着就可以刷面试题了,下面这些面试题都是小编精心整理的,贴给大家看看。

①大厂高频45道笔试题(智商题)

②BAT大厂面试总结(部分内容截图)

③面试总结

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

算法汇集笔记

[外链图片转存中…(img-xUlhDTBB-1711386083225)]

其他像Spring,SpringBoot,SpringCloud,SpringCloudAlibaba,Dubbo,Zookeeper,Kafka,RocketMQ,RabbitMQ,Netty,MySQL,Docker,K8s等等我都整理好,这里就不一一展示了。

[外链图片转存中…(img-miJn5YIY-1711386083225)]

2、狂刷面试题

技术主要是体现在平时的积累实用,面试前准备两个月的时间再好好复习一遍,紧接着就可以刷面试题了,下面这些面试题都是小编精心整理的,贴给大家看看。

①大厂高频45道笔试题(智商题)

[外链图片转存中…(img-PkFqaUR0-1711386083225)]

②BAT大厂面试总结(部分内容截图)

[外链图片转存中…(img-nTC4rvdL-1711386083226)]

[外链图片转存中…(img-aQwZRW0t-1711386083226)]

③面试总结

[外链图片转存中…(img-YrctQGaj-1711386083226)]

[外链图片转存中…(img-mCnfA9KP-1711386083226)]

3、结合实际,修改简历

程序员的简历一定要多下一些功夫,尤其是对一些字眼要再三斟酌,如“精通、熟悉、了解”这三者的区别一定要区分清楚,否则就是在给自己挖坑了。当然不会包装,我可以将我的简历给你参考参考,如果还不够,那下面这些简历模板任你挑选:

[外链图片转存中…(img-oyHJKm8F-1711386083226)]

以上分享,希望大家可以在金三银四跳槽季找到一份好工作,但千万也记住,技术一定是平时工作种累计或者自学(或报班跟着老师学)通过实战累计的,千万不要临时抱佛脚。

另外,面试中遇到不会的问题不妨尝试讲讲自己的思路,因为有些问题不是考察我们的编程能力,而是逻辑思维表达能力;最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值