您的当前位置:首页正文

ThreadLocal源码阅读笔记

2024-11-30 来源:个人技术集锦

一、功能描述

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;

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)

搞清楚了弱引用的作用后,继续看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的结构有了一定的掌握。

显示全文