ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

ThreadLocal源码阅读笔记

2022-01-13 14:02:30  阅读:129  来源: 互联网

标签:ThreadLocalMap 笔记 ThreadLocal 源码 线程 value key Entry


一、功能描述

ThreadLocal解决了访问共享变量的阻塞问题,并且不需要像CAS操作一样牺牲CPU资源,它为每一个线程维护了一个变量副本,每个线程在访问ThrealLocal里面的变量时实际上访问的是自己线程内的变量副本,并且这个线程内的变量副本与其他线程的变量副本相互隔离,互不影响。也就是说,ThreadLocal包裹的变量是线程级变量。

二、源码解读

ThreadLocal通过一个内部类ThreadLocalMap进行数据的保存,并将自己本身作为key,从get方法入手。

public T get() {
  // 取得当前线程
  Thread t = Thread.currentThread();
  // 取得当前线程内的ThreadLocalMap
  ThreadLocalMap map = getMap(t);
  if (map != null) {
      // 使用threadlocal作为key在ThreadLocalMap内取得Entry对象
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
          @SuppressWarnings("unchecked")
          // 取得threadlocal包裹的值在该线程内的副本
          T result = (T)e.value;
          return result;
      }
  }
  return setInitialValue();
}

在get方法内发现,Threadlocal首先获取了当前线程,然后使用当前线程作为key取得ThreadLocalMap对象,那么这个ThreadLocalMap对象仅对当前线程可见,ThreadLocalMap内包含的内容也仅对当前线程可见,查看getMap方法:

ThreadLocalMap getMap(Thread t) {
   return t.threadLocals;
}

此时发现ThreadLocalMap实际上保存在Thread类的threadLocals变量中,查看Thread类代码,其内部保存了ThreadLocalMap类变量threadLocals,即ThreadLocalMap定义在ThreadLocal类中,却实际保存在Thread类中。

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

image
观察ThreadLocalMap类的定义

static class ThreadLocalMap {
    // 静态内部类,保存键值对,并且使用了弱引用
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

使用弱引用的原因主要是为了帮助jvm进行垃圾回收(可以参考WeakHashMap)
image
通过内存引用关系图可以发现,对ThreadLocal的引用有两处,即自定义的threadlocal变量和Entry内的key,如果Entry内的key持有ThreadLocal的强引用,那么即使将自定义的threadlocal变量设置为空,由于key的存在,也无法对threadlocal所占用的内存进行回收,就会造成内存泄漏问题。
Entry内的key持有ThreadLocal的弱引用,当自定义的threadlocal变量设置为null时,只有key引用到了java堆内的ThreadLocal,因为弱引用的特性,如果没有其他强引用连接,则可以被回收,因此不会造成内存泄漏问题。
如果通过将ThreadLocal设置为null来帮助GC时发现,threadlocal变量可以被回收掉,但是如果之前未将value清空的话,value会一直持有引用,会造成内存泄漏问题。因此当某个线程内的threadlocal使用完了,一定要先调用remove方法清空value,在设置threadlocal为null。

搞清楚了弱引用的作用后,继续看ThreadLocalMap类的getEntry方法

private Entry getEntry(ThreadLocal<?> key) {
    // 通过threadlocal类的hashcode取得Entry在table中的下标
    int i = key.threadLocalHashCode & (table.length - 1);
    // 在table中取得Entry对象
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

通过以上代码发现,ThreadLocalMap实际的存储结构是Entry[] table,而table的结构为hash表,为了验证这一观点,继续查看ThreadLocalMap类的set方法

private void set(ThreadLocal<?> key, Object value) {
    // 取得Entry表
    Entry[] tab = table;
    // 取得表格长度
    int len = tab.length;
    // 通过threadlocal类的hashcode取得Entry在table中的下标位置
    int i = key.threadLocalHashCode & (len-1);
    // 如果Entry[i]不为空,从下标i开始遍历表格
    for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
        // threallocal与entry中的key相同,直接替换值
        if (k == key) {
            e.value = value;
            return;
        }
        // key为null,设置key,value并修改hashcode
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // Entry[i]为空,直接设置key和value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

看完了set方法,此时大致对table的结构有了一定的掌握。
image
设置值时通过threadlocal的hash码与table的长度来获取要存储的下标位置,获取value时也是同样的方式。

标签:ThreadLocalMap,笔记,ThreadLocal,源码,线程,value,key,Entry
来源: https://blog.csdn.net/Nicholas_GUB/article/details/122472255

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有