【06期】面试官:说一下ThreadLocal和内存泄漏的问题

  • 时间:2025-10-21 00:56 作者: 来源: 阅读:3
  • 扫一扫,手机访问
摘要:什么是ThreadLocalThreadLocal是Java中的一个类,它提供了一种将对象与线程关联的机制。每个线程都可以通过ThreadLocal获取自己的独立副本,以保证线程间的数据隔离和线程安全。ThreadLocal的作用主要有以下几个方面:线程隔离:每个线程可以独立地操作自己的副本,互不干扰。线程封闭:通过ThreadLocal,可以将可变的数据封

什么是ThreadLocal

ThreadLocal是Java中的一个类,它提供了一种将对象与线程关联的机制。每个线程都可以通过ThreadLocal获取自己的独立副本,以保证线程间的数据隔离和线程安全。

ThreadLocal的作用主要有以下几个方面:

  1. 线程隔离:每个线程可以独立地操作自己的副本,互不干扰。

  2. 线程封闭:通过ThreadLocal,可以将可变的数据封装在每个线程内部,使其具有线程级别的封闭性。

  3. 上下文传递:ThreadLocal常被用于存储线程相关的上下文信息,如用户身份、请求参数等。

  4. 避免传参:有些情况下,某些数据需要在多个方法之间传递,使用ThreadLocal可以避免频繁的参数传递。

ThreadLocal是如何工作的?

ThreadLocal核心逻辑都是在Thread类的静态内部类ThreadLocalMap中,接下来一起分析下ThreadLocalMap源码,逐个方法解析:

ThreadLocalMap类中有个静态内部类Entry,Entry可以理解为存储数据的地方。源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {  /**
   * The value associated with this ThreadLocal.
   */  Object value;

  Entry(ThreadLocal<?> k, Object v) {   super(k);
   value = v;
  }
}

通过上边源码发现Entry竟然继承WeakReference,而且ThreadLocal竟然是个弱引用。

弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了「只具有弱引用」的对象,不管当前内存空间足够与否,都会回收它的内存

也就是说ThreadLocal随时可能消失,也就是entry.get()返回的ThreadLocal随时可能为空。

//这个线程的关于所有的ThreadLocal保存的数据都在这,通过ThreadLocal确定下坐标找到对应的entryprivate Entry[] table;private void set(ThreadLocal<?> key, Object value) {
  
        Entry[] tab = table;        int len = tab.length;     //通过threadLocal确定下坐标找到对应的entry        int i = key.threadLocalHashCode & (len-1);    //遍历数组        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();   
            if (k == key) {
                e.value = value;                return;
            }   //这里!!!如果e不是null,但是e.get()是null,表明那个弱引用被垃圾回收了            if (k == null) {                //这个方法会删除所有过期的条目                //意思就是将被垃圾回收掉的threadLocal,从数据table中删除                //并且新new 一个entry取代掉它的i坐标,过分!
                replaceStaleEntry(key, value, i);                return;
            }
        }  //如果没有直接新增一个到数组里边
        tab[i] = new Entry(key, value);        int sz = ++size;        if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
    }

通过上边的set方法,可以发现ThreadLocalMap真正存储数据的地方是table数组里边,通过threadLocal的hash值找下坐标。如果在set的时候发现table中threadLocal被回收掉了,那么它的位置直接被顶替掉。

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

这个get方法没什么好说的,就是用threadLocal的hash值去找table的下坐标,没有找到直接循环数组找。好了ThreadLocalMap的核心逻辑就分析到这。

接下来看ThreadLocal核心源码

public void set(T value) {    // 拿到当前线程
 Thread t = Thread.currentThread();    //获取当前线程下的ThreadLocalMap
 ThreadLocalMap map = getMap(t);    
 if (map != null)
  map.set(this, value); else
  createMap(t, value);
}

ThreadLocalMap getMap(Thread t) { return t.threadLocals;
}public T get() {    // 拿到当前线程
 Thread t = Thread.currentThread();     //获取当前线程下的ThreadLocalMap
 ThreadLocalMap map = getMap(t); if (map != null) {        //通过threadLocal获取table里边的value值
  ThreadLocalMap.Entry e = map.getEntry(this);  if (e != null) {   @SuppressWarnings("unchecked")
   T result = (T) e.value;   return result;
  }
 } return setInitialValue();
}

【06期】面试官:说一下ThreadLocal和内存泄漏的问题


threadLocal和ThreadMap的关系

图解释:

建了三个ThreadLocal,有两个线程,在线程里边ThreadLocalMap类的属性table分别保存着threadLocal对应的value值。

ThreadLocal内存泄漏问题

内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。一般少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。

第一「说一下为什么在ThreadLocalMap中ThreadLocal是弱引用?」

如果在ThreadLocalMap中使用强引用来存储ThreadLocal对象,那么ThreadLocal对象会一直存在于内存中,即使在实际的应用中已经不再需要该ThreadLocal对象。这是由于ThreadLocalMap是与线程相关联的,保存在ThreadLocalMap的table数组中,ThreadLocalMap中的键值对不会被自动清理,而是会一直保留,从而造成内存泄漏。也就是说ThreadLocal永远不会被清理,除非手动清理,ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。

为了解决这个问题,ThreadLocal被存储为弱引用。弱引用的特点是,当一个对象只被弱引用所引用时,在垃圾回收时会被自动回收。当ThreadLocal对象被垃圾回收时,对应的键值对也会被自动从ThreadLocalMap中移除。

「threadLocal内存泄漏问题」

如果一个线程长时间运行,一直持有ThreadLocal对象的键值,由于value被强制引用,没有及时清理导致内存泄漏。

为了避免ThreadLocal内存泄漏问题,需要进行适当的清理操作。可以使用ThreadLocal类提供的remove()方法手动清理ThreadLocal对象关联的值。

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部