Java-JUC-ThreadLocal(1)

//关键

setLocalPage(page);

return page;

}

  • setLocalPage,给ThreadLocalMap设置Page对象

protected static void setLocalPage(Page page) {

LOCAL_PAGE.set(page);

}

  • PageInterceptor拦截器中,调用intercept,完成所有操作调用afterAll

  • afterAll,将当前线程对应的dialectpage清理,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


  • 主要工作
  1. 设置值,如果没有ThradLocalMap就为其创建
  1. 在实际设置的过程中,如果找到k相等的,就替换;如果找到k==null,就进行一次清理工作,并在清理同时,如果找到k相等的,同样替换,如果没有相等的,就放找到为null的地方
  • 获取到当前线程,放value是放入当前线程对于的map里,mapkey为当前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


  • 主要工作
  1. 获取ThreadLocal实例对象对应的值,如果没有就返回null
  1. 如果当前Thread没有ThreadLocalMap为其创建,并将ThreadLocal-null加入
  1. 搜索时,第一次尝试直接命中,如果找不到,尝试遍历搜索,同时清理k == nullEntry
  • 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,遍历搜索,在遍历的同时,清除为nullEntry

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);

}

  • ThreadLocalMapremove,遍历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方法中,会对knull进行判断并清理

  • 如果使用强引用,当不需要使用当前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越来越大
  1. 没有手动删除Entry
  1. 线程一直存在,ThreadLocalMap生命周期跟Thread一样
  • 使用弱引用,避免ThreadLocalMap中仍指向ThreadLocal无法被回收

  • 使用完毕后,要及时remove,防止Entry指向的value不能被及时回收

知其然不知其所以然,大厂常问面试技术如何复习?

1、热门面试题及答案大全

面试前做足功夫,让你面试成功率提升一截,这里一份热门350道一线互联网常问面试题及答案助你拿offer

2、多线程、高并发、缓存入门到实战项目pdf书籍

3、文中提到面试题答案整理

4、Java核心知识面试宝典

覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

片转存中…(img-a6VQYRf1-1714320723392)]

[外链图片转存中…(img-rttpBjko-1714320723393)]

[外链图片转存中…(img-edTfTuVC-1714320723393)]

3、文中提到面试题答案整理

[外链图片转存中…(img-o9zsCAzh-1714320723394)]

4、Java核心知识面试宝典

覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入

[外链图片转存中…(img-LLoQ1FfB-1714320723394)]

[外链图片转存中…(img-PAP7ITTj-1714320723395)]

[外链图片转存中…(img-VEYSwOFl-1714320723395)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值