Skip to content

HashMap 的底层原理是什么? 扩容机制?线程不安全会导致什么问题?ConcurrentHashMap 如何保证线程安全?

  • HashMap 的底层原理:数组 + 链表 + 红黑树(JDK 1.8+)。当链表长度超过 8 且 数组(table)的长度大于等于 64 时,链表会自动转换为 红黑树。

  • 扩容机制:默认初始容量为 16,当元素数量超过 16 * 0.75 = 12 时,就会扩容。

  • 线程不安全:同时put同一个位置数据覆盖。jdk1.7之前头插法导致链表死循环。另一个线程扩容时可能get到null。

  • ConcurrentHashMap 的线程安全:分段锁(只锁桶的头节点) + CAS(乐观锁)。发生哈希冲突时,会锁桶的头节点,然后使用CAS操作put。发生扩容时,会锁整个table。

synchronized 和 ReentrantLock 的区别是什么?

  • 使用方式:ReentrantLock需要手动加锁和释放,不释放会导致死锁。

  • 可中断:synchronized 不可中断,会一直阻塞,ReentrantLock 可中断,可设置超时。

  • 公平性:synchronized 只有非公平锁,不保证等待时间最长的线程优先获取锁,可能产生线程饥饿现象。ReentrantLock 可选择公平性。

  • 多等待条件:ReentrantLock 可以设置多个等待条件。

简单场景用synchronized,复杂业务用ReentrantLock。

线程池的核心参数?

  • 基础线程数
  • 最大线程数
  • 空闲线程存活时间
  • 工作队列
  • 线程工厂
  • 拒绝策略

线程池的工作原理是什么?

  1. 核心线程未满 → 创建核心线程
  2. 核心线程已满 → 放入工作队列
  3. 队列已满 → 创建临时线程
  4. 所有资源耗尽 → 执行拒绝策略

你是如何根据业务场景配置线程池的?

  • 大量计算,很少IO等待(如数据处理、复杂算法),线程数 ≈ CPU核数,避免过多线程上下文切换。

  • 大量网络/磁盘IO,CPU等待时间长(如HTTP请求、数据库操作),线程数 ≈ CPU核数 * 2。

讲一下JVM的内存结构(运行时数据区)。 哪些区域是线程共享的?

线程共享:

  • 堆:存放对象实例。
  • 方法区:存放类信息、静态变量、常量、编译后的代码。

方法区/元空间 => JDK 1.7及之前:永久代,在堆中;JDK 1.8+:元空间,使用本地内存。

线程私有:

  • 栈:存放函数调用信息,例如方法参数和局部变量。
  • 程序计数器:记录当前线程正在执行的字节码地址。线程私有。

常见的垃圾回收算法和垃圾回收器有哪些? 了解G1吗?

  • 标记-清除算法:先标记所有可达对象,然后遍历堆内存,回收未被标记的对象。

  • 标记-整理算法:在标记-清除算法的基础上,再加上整理内存碎片。

  • 复制算法:将内存分为两块,每次只使用一块。先标记然后复制到另一块内存,再释放第一块内存,复制过来的内存使用是连续的不会产生内存碎片。

  • 分代算法:将内存分为几代,比如:新生代、老生代。新生代使用复制算法,老生代使用标记算法。

常见的垃圾回收器有哪些?

常见的垃圾回收器包括:

  • 串行收集器:适合客户端应用

  • 并行收集器:JDK8默认,吞吐量优先

  • CMS:低停顿,但已逐步被G1取代。是一款并行的标记-清除收集器

  • G1:面向服务端的并发收集器:它的核心思想是将堆划分为多个Region,通过跟踪每个Region的回收价值,优先回收收益最大的Region。

曾经在花西子项目,尝试过改为G1,但实验结果发现,在java8下的G1吞吐量并无显著收益。因为OMS系统对低延迟需求并不高。

页脚:版权前显示的信息