八股-2025.10.12
1.ConcurrentHashMap 线程安全的具体实现方式。
A.JDK1.7实现:分段锁(Sement锁)
JDK1.7的ConcurrentHashMap采用的是“分段锁”机制,底层结构是“数组(Segment)+数组(HashEntry)+链表”,核心是将整个哈希吧拆为多个独立的“段”,每个段对应一把锁。
实现机制:
锁的粒度:锁仅用于"Segment”级别——线程操作某个“Segment”时,只需获取该Segment“的锁,其他”Segment“不受影响。
并发能力:理论上,ConcurrentHashMap的最大并发数等于Segment数组的长度(默认16),即最多16个线程可同时操作不同Segment,大幅提升吞吐量。
读写优化:
写操作:必须获取Segment锁,确保同一时间只有一个线程修改该Segment的数据
读操作:无需加锁——因为HashEntry的value和next指针用volatile修饰,能保证读操作看到最新值,且Segment内部结构修改时(如扩容),不会影响已存在的HashEntry引用。
B.JDK1.8实现:CAS+synchronized(取消分段锁,细粒度到节点)
JDK1.8彻底放弃了“分段锁”,借鉴HashMap的“数组+链表/红黑树”结构,改用“CAS无锁操作+synchronized“实现线程安全,锁粒度细化到”哈希桶的头节点“,并发性能进一步提升。
实现机制:
put操作:数组未初始化时,多线程通过CAS竞争初始化标记,只有成功线程能完成初始化,其他线程自旋等待;定位哈希桶后,若桶为空则直接用CAS插入新节点,失败则自旋重试;若桶非空则用synchronized锁定同的头结点,确保单线程修改该桶的链表或红黑树,完成key的更新或插入后,若元素超过阈值则触发扩容。
get操作:全程无锁却能确保线程安全,关在在于Node的value和next指针被volatail修饰:根据Java的内存模型,volatail确保写操作的可见性,线程对value和next的修改能被其他线程立即感知;同时,遍历桶内链表或红黑树时,volatail保证不会出现”半修改“的节点引用,使无锁遍历能正确获取数据。
扩容操作:支持多线程协助以提升效率,先通过CAS将sizeCtl设为扩容状态,防止其他线程同时扩容;原数组哈希桶被分为多个片段,个线程负责迁移不同片段到新数组;线程迁移完一个片段后,用CAS更新迁移进度标记,避免其他线程重复迁移;扩容期间,写操作会先协助迁移当前桶再执行插入,读操作则同时检查原数组和新数组,确保能正确获取数据。
https://www.bilibili.com/video/BV1rp4y1P7ci/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
2.C++ 中堆内存和栈内存的区别?
A.管理方式:
栈内存由编译器自动管理,遵循”先进后出“的原则,当函数调用时,局部变量和函数参数等会被自动压入栈中,函数执行结束后,这些内存会被自动弹出并释放,无需开发者手动干预;而堆内存需要开发者手动申请,和释放,编译器不会自动管理,若忘记释放则会导致内存泄漏。
B.生命周期:
占内存的生命周期与所在的作用域绑定,例如函数内的局部变量,当函数执行完毕胡,栈内存就会被释放,无法在作用域外访问;对内存的生命周期由开发者控制,从new申请开始,到delete释放结束,只要未释放,就可以在多个作用域间共享,但也因此更容易因管理不当导致内存问题。
C.分配效率:
栈内存的分配和释放是连续的,通过移动栈顶指针即可完成,几乎没有额外开销,效率极高;堆内存的分配需要在内存中寻找合适的空闲块,释放时还可能触发合并相邻空闲块的操作,因此效率较低,且频繁分配释放容易产生内存碎片。
D.空间大小:
栈的大小通常是固定的,若分配超过栈的空间,会导致栈溢出;堆内存的大小更灵活,理论上受限于系统的可用内存,适合存储大型数据或动态大小的数据。
E.适用场景:
栈适合存储生命周期短且大小固定的临时数据,如函数参数,局部变量,循环变量等;堆内存适合存储生命周期长,大小动态变化或需要跨作用域共享的数据,如动态数组,大型对象,链表节点等。
https://www.bilibili.com/video/BV1Sepyz7ECL/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
3.synchronized和Lock有什么区别?(JAVA)
https://www.bilibili.com/video/BV1XS4y1S7W1/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
4.说说volatile的用法及原理。(JAVA)
https://www.bilibili.com/video/BV19kDgYQEhH/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
5.联合索引的存储结构与最左前缀原则
A.联合索引的存储结构(InnoDB为例):
InnoDB中联合索引的底层仍基于B+树实现,但和单列索引的区别是:B+树的”索引键“十多列值的组合,而非单一列值。其存储结构有三个特点:
a.所有叶子节点会按照”索引列的定义顺序“排序。这种”先左后右“的排序方式,是”最左匹配原则”的底层基础。
b.和InnoDB的单列二级索引一样,联合索引的叶子节点不存储整行数据,而是存储“主键值”(需要通过主键回表查询完整数据。
c.B+树非叶子节点的索引键存储的是“索引列的组合前缀”,用于引导查询方向。
B.最左前缀原则:联合索引的使用规则
最左前缀原则是指:联合索引只有在查询条件包含“索引最左侧列“,且按索引列顺序排列时,才能生效;若跳过左侧列或打乱顺序,索引会部分失效或完全失效。其本质是”顺应B+树的排序逻辑“,因为B+树按”左侧优先“排序,只有从左列开始匹配,才能利用索引的有序性快速定位;若跳过左列,索引的排序逻辑无法发挥作用,只能走全表扫描。
https://www.bilibili.com/video/BV1894y1i7xE/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
6.说说线程的创建方式。(JAVA)
https://www.bilibili.com/video/BV1Ru4y1n7Lw/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967
7.Redis中的Zset怎么实现的?(JAVA)
https://www.bilibili.com/video/BV1BG4y197wV/?spm_id_from=333.337.search-card.all.click&vd_source=3abe3667e67749032f72d6f512b2a967