ThreadLocal源码阅读
什么是ThreadLocal
ThreadLocal 是 Java 里一种特殊变量,它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。用于线程内共享
使用demo
static ThreadLocal threadLocal=new ThreadLocal(); public static void main(String[] args) { //<1>set threadLocal.set("dd"); test(); } public static void test(){ //线程内共享 打印dd System.out.println(threadLocal.get()); }
ThreadLocal
<1>set方法
java.lang.ThreadLocal#set
public void set(T value) { //获得当前线程对象 Thread t = Thread.currentThread(); //<3>获得t的threadLocals的成员变量 也就是ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) //<4>ThreaLocal为key value为我们的值 map.set(this, value); else //为t初始化一个ThreaLocalMap并设置值 createMap(t, value); }
<3>getMap
java.lang.ThreadLocal#getMap
//返回ThreadLocal.ThreadLocalMap ThreadLocal.ThreadLocalMap getMap(Thread t) { //获得Thread的成员变量threadLocals return t.threadLocals; }
从上面可以看出我们的数据都是存储在ThreadLocalMap,ThreadLocalMap就是一个普通的map通过ThreadLocal对象为key
<4>set
java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. //ThreaLocalMap内部通能数组来保存 通过Entry封装 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; //threadLocalMap是通过线性探测法来解决hash碰撞,当出现hash碰撞查找数组的下一个位置 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } //<5>通过内部内Entry 来存储 key为ThreadLocal value为我们set的值 并存储到数组的具体位置 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
<5>Entry
/** * ThreadLocalMap静态内部类 * WeakReference 为弱引用,当弱引用引用的的对象没有被强引用时也会被回收 * 这里可以发现 value是强引用 * super(k) key为ThreadLocal 传给了父类 则是弱引用 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
<6>get方法
public T get() { //获得当前线程 Thread t = Thread.currentThread(); //<3>获得线程map ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { //根据当前threadLoca 找到对应的entry ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { //获得value @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果为null则触发初始化 return setInitialValue(); } private T setInitialValue() { //初始化 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = getMap(t); //如果map不为null if (map != null) //直接set进去 map.set(this, value); else //创建map设置进去 createMap(t, value); return value; } /** * 默认为空实现 我们可以继承重写达到默认值的效果 * @return */ protected T initialValue() { return null; }
ThreadLocal内存泄露
或者我们使用线程池技术,没有及时remove导致Threadlocal常驻到Thread
根据<5>处我们可以看到ThradLocal没有被强引用时就会被垃圾回收 即Entry Key会被回收,但是value还被Entry引用 ,Entry被ThreadLocalMap引用 ThreadLocalMap被Thread引用,所以在线程未被回收前对象会一直不会被回收,如果使用线程池技术 可能导致内存泄露
如:
public static void main(String[] args) { test(); } public static void test(){ ThreadLocal threadLocal=new ThreadLocal(); threadLocal.set(new Student());; }
弱引用Entry的keyThreadLocal对象会被回收,但是Entry还持有value的引用 ThreadLocalMap持有Entry引用 Thread持有ThreadLocalMap引用 导致内存泄露,所以当使用完要手动调用remove方法
public static void test(){ ThreadLocal threadLocal=new ThreadLocal(); threadLocal.set("ddd");; threadLocal.remove(); }
key使用弱引用是下次使用Thread.remove方法会回收key为null的
private void remove(ThreadLocal<?> key) { //使用hash方式,计算当前ThreadLocal变量所在table数组位置 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //再次循环判断是否在为ThreadLocal变量所在table数组位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //调用WeakReference的clear方法清除对ThreadLocal的弱引用 e.clear(); //清理key为null的元素 expungeStaleEntry(i); return; } } }
总结
1.ThreadLocal本质获取当前线程对象的成员变量ThreadLocalMap ThreadLocalMap通过ThreadLocal作为key存储相应的value value通过Entry封装
2.使用map的原因是因为一个线程可以定义多个ThreadLocal
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!