乐趣区

关于java:Java源码解析ThreadLocal

本文由 colodoo(纸伞)整顿

参考源码:jdk1.8.0_131

我置信大部分的人对 ThreanLocal 都一脸懵逼的,因为我一开始也是这样的,直到在面试中被问到当前,我才第一次晓得 ThreanLocal 的存在。

这是一些结论性的介绍,足以应酬面试题:

  • ThreadLocal 是 Java 中所提供的线程本地存储机制,能够利⽤该机制将数据缓存在某个线程外部,该线
    程能够在任意时刻、任意⽅法中获取缓存的数据
  • ThreadLocal 底层是通过 ThreadLocalMap 来实现的,每个 Thread 对象(留神不是 ThreadLocal
    对象)中都存在⼀个 ThreadLocalMap,Map 的 key 为 ThreadLocal 对象,Map 的 value 为须要缓
    存的值
  • 如果在线程池中使⽤ ThreadLocal 会造成内存透露,因为当 ThreadLocal 对象使⽤完之后,应该
    要把设置的 key,value,也就是 Entry 对象进⾏回收,但线程池中的线程不会回收,⽽线程对象
    是通过强引⽤指向 ThreadLocalMap,ThreadLocalMap 也是通过强引⽤指向 Entry 对象,线程
    不被回收,Entry 对象也就不会被回收,从⽽呈现内存透露,解决办法是,在使⽤了
    ThreadLocal 对象之后,⼿动调⽤ ThreadLocal 的 remove ⽅法,⼿动分明 Entry 对象
  • ThreadLocal 经典的应⽤场景就是连贯治理(⼀个线程持有⼀个连贯,该连贯对象能够在不同的⽅法之
    间进⾏传递,线程之间不共享同⼀个连贯)

而后再从源码的角度来解释这些论断。

set

java.lang.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);
}
  • 获取以后线程
  • 获取以后线程中的本地存储对象
  • 把值设置到以后线程的本地存储对象中

从这里就能够看出,ThreadLocal 作用域是以后线程(currentThread),不同线程之间是隔离的。

set

java.lang.ThreadLocal.ThreadLocalMap#set

private void set(ThreadLocal<?> key, Object value) {

    // Entry 数组赋值给长期变量 tab
    Entry[] tab = table;
    // 获取 Entry 数组长度
    int len = tab.length;
    // 依据 key 通过 hash 算法获取下标
    int i = key.threadLocalHashCode & (len-1);

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

        // 如果存在则更新
        if (k == key) {
            e.value = value;
            return;
        }

           // 替换古老条目
        // TODO:这里的逻辑简单不深究
        if (k == null) {replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 否则新增节点
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();}

getMap

java.lang.ThreadLocal#getMap

ThreadLocalMap getMap(Thread t) {
    // 获取线程中的 ThreadLocalMap
    return t.threadLocals;
}

createMap

java.lang.ThreadLocal#createMap

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

ThreadLocalMap

java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal<?>, java.lang.Object)

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 实例化 Entry 数组
    table = new Entry[INITIAL_CAPACITY];
    // 计算下标
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 新增节点到数组中
    table[i] = new Entry(firstKey, firstValue);
    // 设置尺寸 =1
    size = 1;
    // 设置阈值
    setThreshold(INITIAL_CAPACITY);
}

get

java.lang.ThreadLocal#get

public T get() {
    // 获取以后线程
    Thread t = Thread.currentThread();
    // 获取以后线程 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 如果 ThreadLocalMap 不为空
    if (map != null) {
        // 获取 Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 获取后果不为空,返回后果
        if (e != null) {@SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 否则设置初始化值
    return setInitialValue();}

图解

以下为帮忙记忆的一张图。

退出移动版