ThreadLocal学习笔记

ThreadLocal学习笔记

最近看一些面经,好像很多时候都有提到ThrealLocal的实现原理,之前在学习多线程的时候似乎没有太注意ThreadLocal这个本地线程,现在看看源码,学习一下。

ThreadLocal的实现原理

ThreadLocal是线程内部的数据存储类,通过它可以指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取数据.

自定义ThreadLocal实现

自定义实现一个ThreadLocal,先定义一个map,将当前线程作为key,线程拥有的数据作为值存入map,实现value为当前线程私有。


/**
 * @ClassName MyThreadLocal
 * @Description: 自定义一个简易ThreadLocal
 * @Author zhouyang
 * @Date 2019/5/22 下午9:55.
 */

public class MyThreadLocal<T> {

    //基于map实现
    private Map<Thread,T> values = new HashMap<>();

    //存值
    public synchronized void set(T value){
        values.put(Thread.currentThread(),value);
    }

    //取值
    public synchronized T get(){
        return values.get(Thread.currentThread());
    }
}

自定义可以完成基本功能,但是在多线程中性能较差,会发生冲突。

源码解读

源码中的set和get方法,保证了各个线程之间的数据互不干扰。


 public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

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

    

可以发现,每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行set方法中,是从当前线程的threadLocals变量获取。
所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。

ThreadLoalMap

ThredLocalMap是ThreadLocal中的一个内部类,用于存储线程的ThreadLocalMap对象。


static class ThreadLocalMap {
        // 自定义Entry类用于存储<ThreadLocal, Value>键值对.
        static class Entry extends  WeakReference<ThreadLocal> {
            Object value;
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

        private Entry[] table;
        private static final int INITIAL_CAPACITY = 16;
        private int threshold;

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            // 使用数组来模拟实现Map.
            table = new Entry[INITIAL_CAPACITY];
            // 使用ThreadLocal的HashCode来生成下标,尽量减少哈希碰撞
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        // 设置扩容resize时的阈值
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
    }

在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,是不是很神奇,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
这里需要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。

hash冲突

在ThreadLocalMap中,数据的存入源码:

 /**
       * Set the value associated with key.
       *
       * @param key the thread local object
       * @param value the value to be set
       */
      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.

          Entry[] tab = table;
          int len = tab.length;
          int i = key.threadLocalHashCode & (len-1);

          for (Entry e = tab[i];
               e != null;
               e = tab[i = nextIndex(i, len)]) {
              ThreadLocal<?> k = e.get();

              if (k == key) {
                  e.value = value;
                  return;
              }

              if (k == null) {
                  replaceStaleEntry(key, value, i);
                  return;
              }
          }

          tab[i] = new Entry(key, value);
          int sz = ++size;
          if (!cleanSomeSlots(i, sz) && sz >= threshold)
              rehash();
      }

在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:

  • 1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
  • 2、不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
  • 3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;
    这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
    可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。

内存泄露

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("crazyang");
    // 其它业务逻辑
} finally {
    localName.remove();
}

public static final ThreadLocal listOfSuperPermissionObjectTreeResponse = new ThreadLocal; 这是一个具有公共、静态和最终修饰符的ThreadLocal变量。ThreadLocal是Java用于在每个线程保存变量副本的类。在这种情况下,listOfSuperPermissionObjectTreeResponse是一个ThreadLocal对象,用于在每个线程维护一个特定类型的ThreadLocal类的源码一个内部静态类Entry,它继承自WeakReference<ThreadLocal<?>>,其包含了与ThreadLocal对象关联的。每个ThreadLocal对象都会在Entry维护一个value,用于存储与当前线程相关联的特定。当ThreadLocal对象被垃圾回收时,Entry对象也会被回收。 在源码ThreadLocal.withInitial(HashMap::new)部分是使用Supplier接口的方法,用于在每个线程初始化ThreadLocal对象时提供一个初始。在这种情况下,使用HashMap的构造函数作为初始供应商。 总之,public static final ThreadLocal listOfSuperPermissionObjectTreeResponse = new ThreadLocal;声明了一个ThreadLocal变量,用于在每个线程保存一个特定类型的,并使用HashMap::new作为初始供应商。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [java8源码-ac_babel:一些后端学习笔记整理](https://download.csdn.net/download/weixin_38704485/19390839)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [JAVA并发编程--5 理解ThreadLocal](https://blog.csdn.net/l123lgx/article/details/127439245)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值