一、基础
1.Java中的四种引用类型及其应用场景
1)强引用
**特点:**默认引用方式,对象不会被GC回收。
应用 :常规对象创建、缓存核心数据:用户信息,订单信息
2)软引用
java
SoftReference<Object> ref = new SoftReference<>(new Object());
**特点:**只有内存不足时才回收。
应用:内存敏感的缓存,大对象缓存。内存充足时利用缓存提升性能,内存紧张时,自动释放资源避免oom
3) 弱引用
解决内存泄漏,存储临时关联数据
java
WeakReference<Object> ref = new WeakReference<>(new Object());
**特点:**GC时必定回收
应用:
- WeakHashMap,键为弱引用
- 监听器/回调防止内存泄漏
- ThreadLocal
4)虚引用
**特点:**每次GC回收。无法通过get()获取对象,需配合ReferenceQueue
应用:
- 精确控制对象销毁时机
- 代替finalize()进行资源管理
- 直接内存管理(netty、DirectByteBuffer)
2.HashMap、 ConcurrentHashMap的底层实现原理
jdk1.8 ,数据结构:数组+链表+红黑树。
|-------|----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | hashMap | ConcurrentHashMap |
| 扩容时机 | 元素总数超过阈值(数组长度*0.75) | 写线程,发现目标桶头结点是ForwardingNode,表示正在迁移,则协助迁移 |
| put流程 | 如果数组为空,先初始化数组,默认长度为16 | 如果数组为空,先CAS初始化数组,默认长度为16 |
| put流程 | 计算桶的位置。hashCode高16位和低16位进行异或,然后与数组长度-1相与 ||
| put流程 | 桶为空,直接插入 | 桶为空,CAS插入。 |
| put流程 | 桶不为空,遍历链表/红黑树,判断key是否相同,相同则覆盖,不同则末尾插入 | 桶不为空(上一步CAS失败,或者本身不为空):synchronized锁住头结点。 |
| put流程 | | 判断头结点是否为forwardingNode,是则协助扩容。否则和左边一样的逻辑插入数据。 |
| put流程 | 判断桶数据是否大于8或者小于6?大于8则尝试转为红黑树。小于6则树转为链表。 ||
| put流程 | 判断是否需要扩容 ||
| 扩容流程 | 单线程执行 | 多线程并发执行 |
| 扩容流程 | 1)新建一个容量为原数组2倍的新数组 2)数据迁移,hash&数组长度,为0,数据在原位置;否则在原位置+原数组长度 3)迁移完成,将新数组引用替换旧数组引用(扩容期间整个map不可用,效率低) | 1)新建一个容量为原数组2倍的新数组 2)旧数组划分为多个区间(transferIndex),多个线程协同分批迁移数据 3)当线程进行put时,如果桶是 ForwardingNode,放下 put 的活,去 CAS 抢 transferIndex 领取区间,对区间中桶的头结点加锁,协助把旧数组的数据搬到新数组。 4)线程从后往前领取任务区间,步长根据cpu核数和数组长度动态计算。 5)当所有桶的头结点都变成forwardingNode,替换数组引用。 桶的头结点变为forwardingNode,读操作会调用find方法找到新位置 |
3.ArrayList和LinkedList的底层差异及使用场景
ArrayList底层是数组 ,而LinkedList底层是链表
ArrayList适用于随机访问 ,适合读多写少 的场景日常开发中最常用。在尾部插入时效率高,不用移动大量元素 。当数组容量不足时,会自动触发扩容,默认是原来的1.5倍。扩容时需要复制数组,有一定开销。
LinkedList适用于**写多读少,**需要在头部尾部频繁插入/删除的场景。头尾插入时,效率高。如果是其他节点,虽然不用迁移数据,但是需要定位节点,从头遍历。比较适合做队列,先进先出