JAVA 八股

1.1JAVA基础面试

1.2JAVA 集合面试

1.2.1 HashMap 和 Hashtable:区别、联系与实战解析

共同点:

底层数据结构(核心):二者底层都基于「数组 + 链表」(JDK 1.8 后 HashMap 新增红黑树优化)实现哈希表。通过 key.hashCode() 计算哈希值,确定元素在数组中的位置;哈希冲突时,用链表(或红黑树)存储冲突元素。

核心功能:都实现了 Map 接口,支持「增删改查」键值对操作(put/get/remove 等)

键的要求:键都不能重复(重复 put 会覆盖原值);键的 hashCode()equals() 方法会影响哈希表的正确性(需遵循重写规则)。

核心区别

维度 HashMap Hashtable
线程安全 非线程安全(性能高) 线程安全(性能低)
底层实现 JDK 1.8 引入红黑树(链表长度≥8 时转换) 始终是数组 + 链表(无红黑树优化)
null 支持 允许 key/value 为 null(仅 1 个 null key) 不允许 key/value 为 null(抛 NullPointerException)
继承体系 继承 AbstractMap,实现 Map 接口 继承 Dictionary,实现 Map 接口
初始容量 / 扩容 初始容量 16,扩容为 2 倍(16→32→64) 初始容量 11,扩容为 2 倍 + 1(11→23→47)
哈希算法 优化过的哈希算法(减少冲突) 直接使用 key.hashCode()(冲突率较高)
遍历方式 支持快速失败(fail-fast)的迭代器 支持快速失败的枚举(Enumeration)+ 迭代器
默认加载因子 0.75(和 Hashtable 一致) 0.75
使用场景 单线程环境(绝大部分业务场景) 多线程环境(已被 ConcurrentHashMap 替代)

为什么 Hashtable 被淘汰?

Hashtable 的线程安全是通过「给整个方法加 synchronized」实现的,相当于 "整个哈希表一把锁":

  • 线程 A 操作索引 0 的元素,线程 B 操作索引 100 的元素,也会被阻塞;
  • ConcurrentHashMap 采用「分段锁(JDK 1.7)/ CAS + 局部锁(JDK 1.8)」,只锁冲突的桶,并发性能提升 10 倍以上。

1.2.2 为什么ArrayList不是线程安全的,具体来说是哪里不安全?

在高并发添加数据下,ArrayList会暴露三个问题;

  • 部分值为null(我们并没有add null进去)
  • 索引越界异常
  • size与我们add的数量不符
java 复制代码
public boolean add(E e) {
    ensureCapacityInternal(size + 1);// Increments modCount!!
    elementData[size++]=e;
    return true;
}

大体可以分为三步:

  • 判断数组需不需要扩容,如果需要的话,调用grow方法进行扩容;
  • 将数组的size位置设置值(因为数组的下标是从o开始的);
  • 将当前集合的大小加1

下面我们来分析三种情况都是如何产生的:

  • 部分值为null:当线程1走到了扩容那里发现当前size是9,而数组容量是10,所以不用扩容,这时候cpu让出执行权,线程2也进来了,发现size是9,而数组容量是10,所以不用扩容,这时候线程1继续执行,将数组下标索引为9的位置set值了,还没有来得及执行size++,这时候线程2也来执行了,又把数组下标索引为9的位置set了一遍,这时候两个先后进行size++,导致下标索引l10的地方就为null了。
  • 索引越界异常:线程1走到扩容那里发现当前size是9,数组容量是10不用扩容,cpu让出执行权,线程2也发现不用扩容,这时候数组的容量就是10,而线程1set完之后size++,这时候线程2再进来size就是10,数组的大小只有10,而你要设置下标索引为10的就会越界(数组的下标索引从0开始);
  • size与我们add的数量不符:这个基本上每次都会发生,这个理解起来也很简单,因为size++本身就不是原子操作,可以分为三步:获取size的值,将size的值加1,将新的size值覆盖掉原来的,线程1和线程2拿到一样的size值加完了同时覆盖,就会导致一次没有加上,所以肯定不会与我们add的数量保持一致的;

1.2.3 什么是CAS

CAS 是「比较并交换」(Compare And Swap)的缩写,是无锁并发编程 的核心技术(也是 ConcurrentHashMapAtomicInteger 等并发工具的底层实现)。它解决了传统加锁(synchronized)导致的性能问题,用硬件级别的原子操作保证并发安全。

核心定义:CAS 是一种原子指令(由 CPU 硬件层面保证原子性),它的逻辑可以概括为:我认为变量的当前值是 A,只有当实际值确实是 A 时,才把它改成 B;如果实际值不是 A(说明被其他线程改了),就什么都不做,返回 "失败"。

CAS vs synchronized(核心对比)

维度 CAS synchronized
锁类型 无锁(自旋) 独占锁(阻塞)
原子性保证 CPU 硬件指令 JVM 层面的锁机制
性能 高(无上下文切换) 低(高并发下阻塞 / 唤醒开销)
适用场景 单个变量的高频读写 多变量 / 复杂逻辑的并发控制
问题 ABA、自旋开销 死锁、线程阻塞

1.3 JAVA并发

相关推荐
va学弟2 小时前
Java 网络通信编程(6):视频通话
java·服务器·网络·音视频
我是唐青枫2 小时前
C#.NET Span 深入解析:零拷贝内存切片与高性能实战
开发语言·c#·.net
pjw198809032 小时前
Spring Framework 中文官方文档
java·后端·spring
lxh01132 小时前
数据流的中位数
开发语言·前端·javascript
盒马盒马2 小时前
Rust:迭代器
开发语言·后端·rust
jgyzl2 小时前
2026.3.11MyBatis-Plus基本使用与思考
java·数据库·mybatis
Full Stack Developme3 小时前
Java 常用通信协议及对应的框架
java·开发语言
( •̀∀•́ )9203 小时前
Spring Boot 启动报错 `BindException: Permission denied`
java·spring boot·后端
杰克尼3 小时前
苍穹外卖--day10
java·数据库·spring boot·mybatis·notepad++