PageInterceptor
拦截器中,调用intercept
,完成所有操作调用afterAll
afterAll
,将当前线程对应的dialect
和page
清理,remove
掉
public void afterAll() {
AbstractHelperDialect delegate = this.autoDialect.getDelegate();
if (delegate != null) {
delegate.afterAll();
this.autoDialect.clearDelegate();
}
clearPage();
}
getDelegate
实际上也是从ThreadLocal
获取当前线程的AbstractHelperDialect
,应用了代理模式,最终由PageHelper
来增强删除
public AbstractHelperDialect getDelegate() {
return this.delegate != null ? this.delegate : (AbstractHelperDialect)this.dialectThreadLocal.get();
}
clearPage
调用remove
public static void clearPage() {
LOCAL_PAGE.remove();
}
Set
- 主要工作
- 设置值,如果没有
ThradLocalMap
就为其创建
- 在实际设置的过程中,如果找到
k
相等的,就替换;如果找到k==null
,就进行一次清理工作,并在清理同时,如果找到k
相等的,同样替换,如果没有相等的,就放找到为null
的地方
- 获取到当前线程,放
value
是放入当前线程对于的map
里,map
的key
为当前ThreadLocal
对象
public void set(T value) {
//当前线程
Thread t = Thread.currentThread();
//获取map
ThreadLocalMap map = getMap(t);
if (map != null)
//key - 当前ThreadLocal的实例对象
map.set(this, value);
else
//没有则创建
createMap(t, value);
}
getMap
对应的为Thread
的成员变量threadLocals
,每出现一个线程,就会初始化一个ThreadLocalMap类型的threadLocals
,专属于的该线程的map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
createMap
初始化当前线程对应的ThreadLocalMap
void createMap(Thread t, T firstValue) {
//当前ThreadLocal的实例对象作为key
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap
构造
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//table 为 Entry(继承了WeakReference)数组 INITIAL_CAPACITY 默认 16
//容量必须是2的整数次幂
table = new Entry[INITIAL_CAPACITY];
//线性探测法,找到一个下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置扩容阈值,当大于它时,需要扩容
setThreshold(INITIAL_CAPACITY);
}
set
,实际进行set
的操作,在当前线程对应的map
中遍历
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//遍历所有的Entry 线性探测法
for (Entry e = tab[i];
e != null;
//实际上是循环遍历 i + 1 < len ? i + 1 : 0
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { //相等则替换 不相等,就找下一个,此时hash冲突了
e.value = value;
return;
}
if (k == null) {
//发现一个为null的key
//1.第一次遍历做一次整体的清理,并保存第一个为null的地方,防止后续突然增加大量数据
//2.第二次遍历找跟当前key是否有相等的,有或没有都放到i的位置,原先的位置置null
//3.清理所有entry
指向null的下标
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//清理为null的元素
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get
- 主要工作
- 获取
ThreadLocal
实例对象对应的值,如果没有就返回null
- 如果当前
Thread
没有ThreadLocalMap
为其创建,并将ThreadLocal-null
加入
- 搜索时,第一次尝试直接命中,如果找不到,尝试遍历搜索,同时清理
k == null
的Entry
get
,范型写法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据ThreadLocal
实例对象获得Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//抑制没被使用的警告
@SuppressWarnings(“unchecked”)
//强转
T result = (T)e.value;
return result;
}
}
//当前线程没有对应的map
或者 没有找到当前key对应的value 返回null
return setInitialValue();
}
getEntry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//看看能不能正好找到
if (e != null && e.get() == key)
return e;
else //找不到就遍历
return getEntryAfterMiss(key, i, e);
}
getEntryAfterMiss
,遍历搜索,在遍历的同时,清除为null
的Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { //没找到 返回null
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null) //找到为null的,直接清除
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
setInitialValue
,为其创建一个map
private T setInitialValue() {
//返回null
T value = initialValue();
Thread t = Thread.currentThread();
//一样的,返回ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//有map设置当前key
map.set(this, value);
else
//否则为其创建一个
createMap(t, value);
//实际上就是null
return value;
}
-
initialValue
,实际上仅是返回null
,可以继承ThreadLocal
重写此方法,自定义返回初始值 -
不了解的可以看这篇博客:https://www.cnblogs.com/pxza/
protected T initialValue() {
return null;
}
remove
remove
,存在map
,找到key
删除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap
的remove
,遍历map
进行删除
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
//清理这个Entry的同时,做一次整体清理
expungeStaleEntry(i);
return;
}
}
}
Entry
Entry
继承WeakReference
,实际上是指向ThreadLocal实例对象的虚引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
为什么使用弱引用
-
在
set/get
方法中,会对k
为null
进行判断并清理 -
如果使用强引用,当不需要使用当前
ThreadLocal
,将当前ThreadLocal
的实例对象置为空即可被回收,但是在ThreadLocalMap
中的Entry
仍然指向当前ThreadLocal
,无法被回收,会产生内存泄漏
,只有ThreadMap
能被回收,才能回收 -
如果是弱引用,将
ThreadLocal
的生命周期和Thread
解绑,只需要把当前外部使用的ThreadLocal
的实例对象置为空即可,内部Entry
指向的为弱引用,只要GC
就会被回收,但是Entry
中的value
仍然存在,被Entry
对象指向,无法被回收,也会产生内存泄漏
在不使用当前
Entry
时,需要tl.remove();
,调用get/set
中仍然会remove
,但是存在长时间不调用get/set
的情况
- 当线程来自于线程池,在归还线程的时候,
ThreadLocalMap
没有被清理掉,会影响下次使用,并导致空间越来越大
内存泄漏
- 真实原因跟
Entry
是否是弱引用没有关系,根源是使用完ThreadLocal
没有及时remove
,导致Map
越来越大
- 没有手动删除
Entry
- 线程一直存在,
ThreadLocalMap
生命周期跟Thread
一样
-
使用弱引用,避免
ThreadLocalMap
中仍指向ThreadLocal
无法被回收 -
使用完毕后,要及时
remove
,防止Entry
指向的value
不能被及时回收
扩容
–
setThreshold
,初始化为2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
rehash
,先清理
总结
大型分布式系统犹如一个生命,系统中各个服务犹如骨骼,其中的数据犹如血液,而Kafka犹如经络,串联整个系统。这份Kafka源码笔记通过大量的设计图展示、代码分析、示例分享,把Kafka的实现脉络展示在读者面前,帮助读者更好地研读Kafka代码。
麻烦帮忙转发一下这篇文章+关注我
真实原因跟Entry
是否是弱引用没有关系,根源是使用完ThreadLocal
没有及时remove
,导致Map
越来越大
- 没有手动删除
Entry
- 线程一直存在,
ThreadLocalMap
生命周期跟Thread
一样
-
使用弱引用,避免
ThreadLocalMap
中仍指向ThreadLocal
无法被回收 -
使用完毕后,要及时
remove
,防止Entry
指向的value
不能被及时回收
扩容
–
setThreshold
,初始化为2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
rehash
,先清理
总结
大型分布式系统犹如一个生命,系统中各个服务犹如骨骼,其中的数据犹如血液,而Kafka犹如经络,串联整个系统。这份Kafka源码笔记通过大量的设计图展示、代码分析、示例分享,把Kafka的实现脉络展示在读者面前,帮助读者更好地研读Kafka代码。
麻烦帮忙转发一下这篇文章+关注我
[外链图片转存中…(img-kDWiKzJi-1714320749219)]