文章目录
- 前言
- 一、今天学习了什么?
- 二、关于问题的答案
- 1.集合
- 2.JUC
- 02、底层原理
- 03、内存泄漏
- 总结
前言
提示:这里为每天自己的学习内容心情总结;
Learn By Doing,Now or Never,Writing is organized thinking.
目前的想法是,根据 Java Guide 和 JavaLearning 和 小林coding进行第一轮复习,之后根据 Tiger 和 CS-Notes 进行最后的重点复习。
先多,后少。
提示:以下是本篇文章正文内容
一、今天学习了什么?
- 复习集合;
- JUC;
二、关于问题的答案
1.集合
Q:集合的框架,你能说一下你对集合的了解吗?
单列集合(Collection):
- List、链表有序且可重复;
- Set、无序不可重复;
- Queue;队列,FIFO;
双列集合(Map),键值对。
Q:ArrayList 介绍一下?
底层是一个数组,默认容量10,每次扩容变为原来的 1.5 倍,是懒惰初始化的,只有当新增第一个元素的时候,才会真正的初始化。
线程不安全的,新加入元素之前,需要判断数组容量大小是否需要扩容,再去添加元素到集合中。
Q:LinkedList 介绍一下?
底层是Node的双向链表。
Q:HashMap?
底层是 数组+链表/红黑树,默认数组大小16,每次扩容变为原来的2倍,数组的大小始终是2的幂次方。
当链表的长度大于等于8且数组大于64,才会树化,当红黑树的节点个数小于等于6时,红黑树会转化为链表。
2.JUC
今天看了一下 JUC 的 ThreadLocal 的视频:
大厂面试题:
- ThreadLocal 中 ThreadLocalMap 的数据结构和关系?
- ThreadLocal 的 key 是弱引用,这是为什么?
- ThreadLocal 内存泄漏问题你知道吗?
- ThreadLocal 最后为什么要加入 remove() 方法呢?
ThreadLocal 是什么?
ThreadLocal 提供线程局部变量,每个线程访问 ThreadLocal 实例的时候都有自己的、独立初始化的变量副本(自己用自己的变量,不和其他线程共享),可以让每个线程存储私有数据,从而在某种程度上避免了线程安全的问题。
02、底层原理
Thread 中包含 ThreadLocal ,而 ThreadLocal 类中有一个内部类 ThreadLocalMap 。
public class Thread implements Runnable {
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
当每 new 一个线程的时候,该线程就会有 ThreadLocal.ThreadLocalMap 这个属性。
public class ThreadLocal<T> {
static class ThreadLocalMap {}
}
ThreadLocal 的「底层实现原理」是基于 ThreadLocalMap 实现的,当调用 ThreadLocal 的 get() 和 set() 方法,其实调用的是 ThreadLocalMap 的 get() 和 set() 方法。
- set() :首先获取当前线程,然后取出 Thread 类中静态内部类 ThreadLocalMap ,将线程实例为key,需要存储到线程的变量为value,作为键值对,存储到 ThreadLocalMap 中;
- get() :在 ThreadLocalMap 中获取当前线程实例存储到 ThreadLocalMap 中存储的数据;
ThreadLocal 对象并不负责保存数据,它只是一个访问入口。
public void set(T value) {
// 获取当前请求的线程
Thread t = Thread.currentThread();
// 取出 Thread 类内部的 threadLocals 变量(哈希表结构)
ThreadLocalMap map = getMap(t);
if (map != null)
// 将需要存储的值放入到这个哈希表中
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap 的「底层数据结构」是,Entry 数组,一个以 ThreadLocal 实例为 key ,任意对象为 value 的 Entry 对象。
static class ThreadLocalMap {
// 键值对
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
}
03、内存泄漏
「内存泄漏」 是指,不会再被使用的对象或者变量占用内存,不能回收。
在 ThreadLocalMap 中,Entry 对象中的 key 是被 WeakReference 修饰的,使用弱引用,但是 value 是强引用。这个样子设计的目的是,避免内存泄漏。
根据 JVM 垃圾回收时的可达性分析,一直存在 Thread -> ThreadLocalMap -> Entry 的引用链路,如下:
ThreadLocalMap 根据 key 是否为 null 来判断是否清理 Entry,如果 key 不被设置成为 WeakReference 类型,是强引用的话,就一直不会被 GC 回收,key 就永远不为 null,那么 Entry 元素就不会被清理。
被弱引用着的对象,无论当前内存是否充足,只要发生垃圾回收都会被回收掉。
ThreadLocal 的设计者认为只要 ThreadLocal 所在的作用域结束了工作被清理了,GC 回收的时候就会把 key 引用对象回收,key 置为 null,ThreadLocal 会尽力保证 Entry 清理掉来最大可能避免内存泄露。
如果 ThreadLocal 一直有强引用应该怎么办,是不是有内存泄露的风险?
最佳实践是用完 ThreadLocal 对象时手动调用 remove 函数。
必须使用 remove() 回收自定义的 ThreadLocal 变量,尤其是在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄漏等问题,尽量在代码中使用使用 try-finally 代码块进行回收。
总结
提示:这里对文章进行总结:
学习进度需要加快。