ThreadLocal学习笔记

    xiaoxiao2022-07-07  177

    文章目录

    ThreadLocal学习笔记ThreadLocal的实现原理自定义ThreadLocal实现源码解读ThreadLoalMaphash冲突内存泄露

    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(); }
    最新回复(0)