Appearance
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。
线程池的核心参数?
- 基础线程数
- 最大线程数
- 空闲线程存活时间
- 工作队列
- 线程工厂
- 拒绝策略
线程池的工作原理是什么?
- 核心线程未满 → 创建核心线程
- 核心线程已满 → 放入工作队列
- 队列已满 → 创建临时线程
- 所有资源耗尽 → 执行拒绝策略
你是如何根据业务场景配置线程池的?
大量计算,很少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系统对低延迟需求并不高。