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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal源码Java中一个关键的类,它提供了一种在多线程环境下实现线程本地变量的机制。在JDK 8之前和之后,ThreadLocal的内部结构有所变化。ThreadLocal源码分为两部分:ThreadLocal类和ThreadLocalMap类。 ThreadLocal类是一个泛型类,它包含了两个核心方法:set()和get()。set()方法用于将一个值与当前线程关联起来,get()方法用于获取当前线程关联的值。 ThreadLocalMap类是ThreadLocal的内部类,它用于存储每个线程的本地变量。在JDK 8之前,ThreadLocalMap是通过线性探测法解决哈希冲突的,每个ThreadLocal对象都对应一个Entry对象,Entry对象包含了ThreadLocal对象和与之关联的值[2]。 在JDK 8之后,ThreadLocalMap的实现方式发生了改变。使用了类似于HashMap的方式,采用了分段锁的机制来提高并发性能。每个线程维护一个ThreadLocalMap对象,其中的Entry对象也是采用链表的形式来解决哈希冲突。 总结起来,ThreadLocal源码主要由ThreadLocal类和ThreadLocalMap类组成。ThreadLocal类提供了set()和get()方法来管理线程本地变量,而ThreadLocalMap类则负责存储每个线程的本地变量,并解决哈希冲突的问题。 史上最全ThreadLocal 详解 ThreadLocal源码分析_02 内核(ThreadLocalMap) 【JDK源码】线程系列之ThreadLocal 深挖ThreadLocal ThreadLocal原理及内存泄露预防 ThreadLocal原理详解——终于弄明白了ThreadLocal ThreadLocal使用与原理 史上最全ThreadLocal 详解。 ThreadLocal源码分析,主要有ThreadLocal源码以及ThreadLocal的内部结构在jdk8前后的变化。 使用方式非常简单,核心就两个方法set/get public class TestThreadLocal { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { try { threadLocal.set("aaa"); Thread.sleep(500); System.out.println("threadA:" threadLocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { threadLocal.set("bbb"); System.out.println("threadB:" threadLocal.get()); } }).start(); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值