1. 说说 Java 中 HashMap 的原理?
- 基本说明
HashMap是基于key-value对的集合类; jdk1.7使用数组+链表的方式, 而1.8使用数组+链表+红黑树的方式实现; 可以根据key快速查询; 是一个线程不安全的, 有一种情况: 在1.7使用的头插法在多线程下会可能产生回环的问题, 在1.8改为尾插法. - 扩容机制
HashMap默认最大capacity(容量)是16, 负载因子(HashMap长度/capacity)默认是0.75, 默认情况下当HashMap的长度大于12的时候会出发自动扩容, 新的容量是旧容量的2倍; - 红黑树的引入
由于1.8引入了红黑树, 在某些条件下链表会转表为数组:- 链表=>红黑树
如果某个桶中的元素数量大于等于 8,且数组长度大于等于 64 时,链表转换为红黑树 - 红黑树=>链表
某个桶中的元素数量小于等于6时, 红黑树会转换为链表
- 链表=>红黑树
2. Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别?(461)
jdk1.7 | jdk1.8 | |
---|---|---|
锁机制 | 使用分段锁(Segment),默认16个独立的Segment允许最多16个线程并发 | 移除Segment,采用更细粒度的锁,在节点级别: 首次使用CAS操作,其余在对链表或红黑树的头节点使用synchronized。 |
数据结构 | 数组+链表 | 数组+链表+红黑树 |
扩容机制 | 每个Segment是一个HashMap, 每个Segment都是独立扩容 | 引入了一种新的扩容策略——渐进式扩容, 多个线程协作逐步进行, 会分批迁移到新数组中, 从而减少了扩容期间的性能开销; 在扩容过程中,使用了 CAS(Compare-And-Swap)操作来保证线程安全性,而不需要锁住整个数组 |
size计算方式 | 尝试不加锁计算3次, 三次结果相同则返回当前值, 如果不同则会加锁遍历其他修改线程将会堵塞 | 基于CountCell的数组,每个线程在自己对应的下标地方进行累加,等最后的时候把数组里面的数据统一获取最终的值 |
3. Java 中有哪些集合类?
4. 为什么 Java 8 移除了永久代(PermGen)并引入了元空间(Metaspace)
Java 8 移除永久代并引入元空间,主要是为了解决 PermGen 固定大小、容易导致内存溢出、GC 效率低的问题。元空间使用本地内存,具备更灵活的内存分配能力,提升了垃圾收集和内存管理的效率。
5. Java 线程池核心线程数在运行过程中能修改吗?如何修改?
- 修改方式
使用 ThreadPoolExecutor.setCorePoolSize(int corePoolSize) 方法可以动态修改核心线程数。corePoolSize 参数代表线程池中的核心线程数,当池中线程数量少于核心线程数时,会创建新的线程来处理任务。这个修改可以在线程池运行的过程中进行,立即生效。 - 注意事项
- 核心线程数的修改不会中断现有任务,新的核心线程数会在新任务到来时生效。
- setCorePoolSize() 方法可以减少核心线程数,但如果当前线程池中的线程数量超过了新的核心线程数,多余的线程不会立即被销毁,直到这些线程空闲后被回收。
6. Java 中如何创建多线程? 简单 后端 Java Java并发
- 实现 Runnable 接口
- 继承Thread方法
- 使用Callable和FutureTask
- 使用线程池ExecuteService
- Completable(本质也是线程池,默认 forkjoinpool)
评论区