滴滴Java基础专项面经:HashMap红黑树、ConcurrentHashMap、ThreadLocal、WeakReference

32 阅读6分钟

上篇聊完架构师技术决策,这篇进入滴滴Java基础专项。Java基础是Android面试的必考内容——HashMap底层实现(红黑树转换)、ConcurrentHashMap并发安全、ThreadLocal线程隔离、WeakReference内存管理。

今天8道题覆盖滴滴Java基础面试核心考点。

Q1:HashMap什么时候链表转红黑树?

条件:同一hash桶链表长度>=8且数组长度>=64时,链表转红黑树。数组<64优先扩容。

为什么是8:根据泊松分布,链表达到8的概率极低(<0.00000006)。8是空间和时间的平衡。

红黑树特性:查找/插入/删除O(logn)(链表O(n))。节点占用空间是链表的2倍。树节点<=6时退化回链表。

追问:HashMap的hash计算为什么用(h = key.hashCode()) ^ (h >>> 16)?高16位异或低16位,让高位也参与hash计算,减少hash冲突(尤其是数组较小时只用低位容易导致冲突)。

Q2:ConcurrentHashMap怎么保证线程安全?

JDK 1.7:分段锁(Segment数组,每个Segment独立加锁)。并发度=Segment数量(默认16个)。

JDK 1.8:CAS + synchronized。空桶用CAS直接写入,非空桶用synchronized锁住链表头节点(只锁一个桶)。扩容时多线程协助迁移。

size()实现:baseCount + CounterCell数组。每个线程更新不同CounterCell(减少竞争),size()时sum所有CounterCell + baseCount。

追问:ConcurrentHashMap和Hashtable区别?Hashtable全表锁(所有操作都锁整个表,并发度=1)。ConcurrentHashMap锁单个桶(并发度高)。Hashtable不允许null key/value,ConcurrentHashMap也不允许(设计决策——null在并发场景语义模糊)。

Q3:ThreadLocal怎么用?有什么坑?

用法:每个线程持有独立变量副本。常用于:存储当前用户信息、数据库Connection、SimpleDateFormat(非线程安全,每线程一份)。

原理:每个Thread内部有ThreadLocalMap(key是ThreadLocal引用,value是值)。get()从当前线程的ThreadLocalMap中取值。

内存泄漏:key是弱引用(ThreadLocal回收后key变null),value是强引用(不自动回收)。解决:使用完调用remove()。线程池场景尤其重要。

追问:InheritableThreadLocal是什么?子线程可以继承父线程的ThreadLocal值。创建子线程时复制父线程的ThreadLocalMap。但线程池场景不适用(线程复用不是每次都创建子线程)。TransmittableThreadLocal(阿里开源)解决线程池场景。

Q4:WeakReference和SoftReference区别?

强引用(Strong):Object obj = new Object()。只要强引用存在,GC不回收。

软引用(SoftReference):内存不足时回收。适合做缓存(内存充足时保留缓存,内存不足时自动清除)。SoftReference<Bitmap> cache = new SoftReference<>(bitmap)

弱引用(WeakReference):下次GC时回收(无论内存是否充足)。适合弱关联对象。典型应用:ThreadLocalMap的key用WeakReference、WeakHashMap(key被回收时自动从Map移除)。

虚引用(PhantomReference):随时可能回收。用于跟踪对象被GC的活动(配合ReferenceQueue使用)。

追问:Android中WeakReference的常见用法?避免Activity/Fragment泄漏——AsyncTask/Handler内部用WeakReference持有Activity引用。WeakHashMap缓存(key不再使用时自动清除)。LiveData内部用WeakReference管理观察者。

Q5:Java内存模型(JMM)是什么?

核心问题:多核CPU各有缓存,线程在不同核执行时数据可能不一致。JMM定义线程如何通过内存交互。

主内存vs工作内存:变量存在主内存(堆)。线程操作变量时先拷贝到工作内存(CPU缓存),操作完写回主内存。

三个特性:原子性(操作不可中断)、可见性(修改后其他线程立即看到)、有序性(禁止指令重排序)。

volatile:保证可见性和有序性,不保证原子性。适合状态标志位。

追问:synchronized和volatile区别?synchronized保证原子性+可见性+有序性(加锁互斥)。volatile只保证可见性+有序性(不加锁)。synchronized有锁竞争开销,volatile没有。能用volatile解决的不用synchronized。

Q6:synchronized和ReentrantLock区别?

synchronized:JVM内置锁,自动释放(代码块完或异常时)。不可中断。非公平锁。

ReentrantLock:JDK的concurrent包。手动lock/unlock(必须finally中unlock)。可中断(lockInterruptibly)。可选公平/非公平。支持超时锁(tryLock)和Condition。

选择:简单场景用synchronized。需要超时、中断、公平、Condition用ReentrantLock。

追问:什么是锁升级?JDK 1.6的优化——synchronized从偏向锁(单线程反复进入)→ 轻量级锁(CAS自旋)→ 重量级锁(OS互斥量)。根据竞争程度自动升级,不可降级。

Q7:线程池的核心参数是什么?

7个参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(非核心线程空闲存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)、handler(拒绝策略)。

执行流程:核心线程未满则创建核心线程 → 核心满放队列 → 队列满创建非核心线程 → 达最大线程数执行拒绝策略。

拒绝策略:AbortPolicy(默认,抛RejectedExecutionException)、CallerRunsPolicy(调用者线程执行任务)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队列最老任务重新提交)。

追问:Android中常用的线程池配置?IO密集型(网络/文件):core=CPU核数*2,队列用LinkedBlockingQueue。CPU密集型(图片处理/计算):core=CPU核数+1,队列用ArrayBlockingQueue。Android推荐用协程(Dispatchers.IO/Default)替代手动线程池。

Q8:滴滴Java基础面怎么准备?

重点复习:HashMap(链表转红黑树条件+hash计算)、ConcurrentHashMap(CAS+synchronized)、ThreadLocal(原理+内存泄漏+remove)、WeakReference(vs SoftReference)、JMM(volatile+synchronized)、线程池(7参数+执行流程)。

面试技巧:Java基础面考深度——不只背八股文,要能讲清楚底层实现。比如"HashMap为什么用红黑树不用AVL树"、"ConcurrentHashMap的size()怎么实现的"。准备时读JDK源码关键路径。


面试Tips:滴滴Java基础面考深度——HashMap红黑树转换和ConcurrentHashMap并发原理是必考题。ThreadLocal内存泄漏和WeakReference也高频。JMM和线程池经常问。能结合源码关键路径讲清楚加分明显。

下一篇进入滴滴网络编程专项——TCP三次握手、HTTPS加密流程、DNS解析优化、网络状态监听。


读过HashMap源码的同学扣1,你觉得最难理解的部分是什么?

本系列连载中,关注不迷路,下一篇:滴滴中级Android(网络编程专项)面试真题

系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。