ThreadLoacl 源码分析

ThreadLoacl 源码分析

ThreadLoacl#Set方法

ThreadLocal 类中有一个属性是 ThreadLocal.ThreadLocalMap threadLocals

下面的源码关键点就是 ThreadLocalMap 的 set 方法

public void set(T value) {
  // 拿到当前线程
  Thread t = Thread.currentThread();
  // 拿到当前线程中的 ThreadLocalMap 对象
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    // 如果不是空,往ThreadLocalMap set value
    map.set(this, value);
  } else {
    // 如果是空的,初始化一个 ThreadLocalMap ,然后把 set value
    createMap(t, value);
  }
}

ThreadLocalMap#set方法

Entry 的 引用是空的话,也直接覆盖值解释:Entry 的 key 是 threadLocal,但是是一个弱饮用,如果这个threadLocal被回收的话,那么就直接覆盖,后面内存泄露部分会说,为什么是弱引用而不是强引用

整体流程就是,通过 key.threadLocalHashCode & (len-1); 找到一个初始位置

如果位置已经有元素了并且不是自己(哈希冲突),就是就会循环一直往后找(解决哈希冲突的方式),直到找到位置。

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.
	
  // ThreadLocalMap 内存存储就是一个 Entry 数据组
  Entry[] tab = table;
  int len = tab.length;
  // key 是当前 ThreadLocal,threadLocalHashCode是 ThreadLocal 的一个属性
  // 感兴趣的可以去看看 threadLocalHashCode 的生产规则
  // 来确定 value 的初始扫描位置
  int i = key.threadLocalHashCode & (len-1);

  // for 循环来最终确定 value 的位置,结束循环条件是 e == null
  for (Entry e = tab[i];
       e != null;
       e = tab[i = nextIndex(i, len)]) {
    // 如果 Entry 的 引用是当前 threadlocal 的话,就是直接覆盖值了
    if (e.refersTo(key)) {
      e.value = value;
      return;
    }
		// 如果 Entry 的 引用是空的话,也直接覆盖值
    if (e.refersTo(null)) {
      replaceStaleEntry(key, value, i);
      return;
    }
  }

  // 到这里说明 e == null(此哈希槽没有被占用,可以直接用)
  tab[i] = new Entry(key, value);
  int sz = ++size;
  // cleanSomeSlots 是清理 Entry ,如果Entry的key为空的话,就会直接回收
  // 整体就是 如果没有清理到东西,并且占用的 hash槽的占用已经超过了阈值,默认 threshold = len * 2 / 3; 那么就可能触发rehash
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();
}
ThreadLocalMap#rehash And ThreadLocalMap#expungeStaleEntries
private void rehash() {
  // 
  expungeStaleEntries();

  // Use lower threshold for doubling to avoid hysteresis
  // 若在 expungeStaleEntries() 清除过后 size>=len/2(也就是 threshold-threshold/4)则调用 resize() 扩容。
  // 感兴趣的可以看看 resize(), 扩容大小是原来的两倍
  if (size >= threshold - threshold / 4)
    resize();
}
private void expungeStaleEntries() {
  Entry[] tab = table;
  int len = tab.length;
  // 循环整个 Entry[],如果key 为空,那么就直接清理掉
  for (int j = 0; j < len; j++) {
    Entry e = tab[j];
    if (e != null && e.refersTo(null))
      expungeStaleEntry(j);
  }
}

threadlocal操作内存泄漏的原因

ThreadLocal对象就相当于钥匙,Thread 对象需要用钥匙去自己内部 ThreadLocalMap 去拿到值。

ThreadLocalMap是一个哈希数组,key为ThreadLocal对象,Value为一个Object类型的值

这就意味着可以创建多个ThreadLocal来获取不同ThreadLocal往这个Map里存的值

弱引用-解决内存泄漏

ThreadLocal对象就相当于钥匙,如果线程一直没有死,但是 ThreadLocal 对象已经没有地方可以用了(一般用作局部变量的时候,方法执行完,就无法再使用了),如下

public void test(){
  ThreadLocal<String> threadLocal = new ThreadLocal<>();
  threadLocal.set("test");
}

但是 ThreadLocal 的 ThreadLocalMap 还有对它的引用,所以一直无法回收。
所以后面官方就把 ThreadLocalMap 的 key 引用从强引用换成了弱饮用,如果没有其他强引用去引用 ThreadLocal ,在GC 的时候就会被回收,代码中也会把 ThreadLocalMap 中的 Entry 的 key 为 null 的 Entry给清理掉

可能发生内存泄露的情况

但是一般 ThreadLocal 用作类变量,如下

public final static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

有强引用就不会在GC的时候被回收,但是如果线程一直没有死的话(例如线程池),那么 ThreadLocalMap 就会一直壮大。
但是实际上 ThreadLocal 对象一般是有限的,例如三个的话,那么一个线程的 ThreadLocalMap 也只会有 3 个 k(localhost) v(业务对象)

get 方法

跟set方法差不多,就不做概述了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值