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并发

相关推荐
Lumos_7776 分钟前
Linux -- 线程
java·jvm·算法
知兀19 分钟前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV21 分钟前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
chaofan98021 分钟前
GPT-5.5 领衔 Image 2.0:像素级控制时代,AI 绘图告别开盲盒
开发语言·人工智能·python·gpt·自动化·api
User_芊芊君子24 分钟前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
爱码小白42 分钟前
Python 异常处理 完整学习笔记
开发语言·python
c++之路1 小时前
C++20概述
java·开发语言·c++20
Championship.23.241 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
芝士就是力量啊 ೄ೨1 小时前
Python如何编写一个简单的类
开发语言·python
橘子海全栈攻城狮1 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序