目 录CONTENT

文章目录

ThreadLocal与InheritableThreadLocal

FatFish1
2024-10-28 / 0 评论 / 0 点赞 / 66 阅读 / 0 字 / 正在检测是否收录...

Thread与ThreadLocal

在Thread类中有一个ThreadLocalMap类型成员变量,存储当前线程中的threadLocal变量。

ThreadLocal.ThreadLocalMap threadLocals = null;

一般情况下它初始化为null,只有在配置时才会进行构造

对于这个成员变量,在ThreadLocal类中提供了一个getMap方法,用于获取线程中的threadLocals变量。

TheadLocalMap

构造方法

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

ThreadLocalMap构造一个Entry数组,通过哈希计算的方法将key和value分配到对应位置的entry中。entry实现自弱引用。使用弱引用的目的是:用户只是new了一个ThreadLocal对象,所以当用户定义的ThreadLocal对象不再使用之后,ThreadLocal对象及其指向的T对象都应该可以被回收

set方法

ThreadLocalMap的set方法是比较复杂的。

private void set(ThreadLocal<?> key, Object value) {
            //Entry数组
            Entry[] tab = table;
            //Entry数组的长度
            int len = tab.length;
            //获取数组的下标
            int i = key.threadLocalHashCode & (len-1);
            //进入循环,循环条件是tab[i]对于的map非空,遇空停止
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                //通过value值来找对应的key值
                ThreadLocal<?> k = e.get();
                 //如果k已经有了,则直接覆盖,返回
                if (k == key) {
                    e.value = value;
                    return;
                }
                // 如果k为null,则说明这个treadLocal变量被gc回收,这个Entry就是StaleEntry(脏Entry),要进行清理
                if (k == null) {
                    //清理脏entry
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
             // 找到空位置时,new一个entry存key、value
            tab[i] = new Entry(key, value);
            //size初始为0,set一个就加1.
            int sz = ++size;
            //清理其余过期对象,若sz大于threshold(阈值就是长度的三分之二),则需要扩容,重新hash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

set方法的关键点如下:

  • 通过哈希计算entry数组的对应下标,从该下标开始循环。

  • 循环过程中,遇到非空的,取该位置上的entrykey,看看是不是自己这个threadLocal变量,如果是,取对应位置的值。如果值为null说明已经被gc了,这个entry就是个脏entry,需要清理掉再配置值。如果非null,则直接覆盖,因此这个线程重新配置threadLocal时,不会有遗留。值set完后,直接return不继续执行

  • 如果遍历非空的位置一直找不到合适的,会遍历直到一个空位停下,创建一个新entry,然后再清理剩下的过期对象。

ThreadLocal

set方法

构造一个ThreadLocal变量的时候,一般使用set方法进行值的初始化。

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

可见set方法的思路如下:

  • 获取当前线程

  • 获取线程的threadLocalMap对象

  • 如果map非空,调用ThreadLocalMap::set方法配置值

  • 如果map为空,调用createMap方法构造新map

get方法

 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();
   }

可见get方法思路如下:

  • 获取当前线程的threadLocalMap成员变量map

  • 如果map非空,获取对应的entry,取值返回

  • 如果map为空,调用setInitialValue方法把值配置进去

在setInitialValue方法中可以发现与set方法一致,只是多了一个调用initialValue赋值给value的过程,而initialValue不重写就是null。这个思路完成了一个get不到转而初始化的逻辑。

createMap方法

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

直接调用ThreadLocalMap的构造函数构造ThreadLocalMap对象,赋值给线程的成员变量。

getMap方法

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

直接返回线程的threadLocalMap变量

remove方法

    private void remove(ThreadLocal<?> key) {
        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)]) {
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }

remove方法就是计算threadLocal变量对应的哈希位置,调用clrear方法进行清理,同时再清理一波其他的脏entry。

每个线程结束后执行remove的意义在于防止上一轮thread的threadlocal变量带到下一轮产生脏数据。不使用remove可能导致threadLocal对象所指向的T对象有潜在的强引用,导致threadLocal对象也没法被回收。

InheritableThreadLocal

InheritableThreadLocal是一种特殊的可继承的threadLocal,子线程将直接继承父线程的threadLocal值,案例如下:

static final InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws Exception{
    threadLocal.set(1);
    Thread t = new Thread(new Domino(), "d");
    t.start();
    TimeUnit.SECONDS.sleep(2);
}
static class Domino implements Runnable {
    @Override
    public void run() {
        Integer i = threadLocal.get();
        System.out.println(i);
    }
}

案例中输出1,如果用普通的,输出就是null

0

评论区