ThreadLocal源码阅读笔记

2022/1/13 14:03:28

本文主要是介绍ThreadLocal源码阅读笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、功能描述

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时也是同样的方式。



这篇关于ThreadLocal源码阅读笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程