ThreadLocal源码解析

切记,不懂的地方看源码 。 另外,秉着不重复劳动的原则,如果有不理解的地方需要图表来帮助理解的话,请结合JavaGuide的[ThreadLocal 详解](ThreadLocal 详解 | JavaGuide(Java面试 + 学习指南)里面的图表进行理解食用更佳。

ThreadLocal的实现原理

ThreadLocal中用于保存线程的独有变量的数据结构是一个内部类: ThreadLocalMap,也是key-value结构。key就是当前的ThreadLocal对象,而value就是我们想要保存的值。

  • Thread拥有两个成员变量

    • ThreadLocal.ThreadLocalMap threadLocals = null;
    • ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • 它们只是用来区别普通的ThreadLocal和inheritableThreadLocal各自使用的ThreadLocalMap。

  • inheritableThreadLocal可以在创建子线程的时候把父线程类的值传递给子线程。

Thread

接下来来了解我们的Thread类,ThreadLocal的使用是作为Thread的成员变量来体现的。

Thread类的成员和构造方法

Thread的成员变量们

java 复制代码
public
class Thread implements Runnable {
    /** Make sure registerNatives is the first thing <clinit> does. 确保静态的代码先执行*/
    private static native void registerNatives();
    static {
        registerNatives();
    }
        
    private volatile String name; // 线程名
    private int            priority; // 线程优先级
    private Thread         threadQ;
    private long           eetop;

    /* Whether or not to single_step this thread. */
    private boolean     single_step;

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;

    /* JVM state */
    private boolean     stillborn = false;

    /* What will be run. 真正运行方法的对象*/
    private Runnable target;

    /* The group of this thread 线程所在的线程组*/
    private ThreadGroup group;

    /* The context ClassLoader for this thread 上下文类加载器*/
    private ClassLoader contextClassLoader;

    /* The inherited AccessControlContext of this thread */
    private AccessControlContext inheritedAccessControlContext;

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /* 
     * 本文的重点, Thread类拥有一个ThreadLocalMap对象
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * 从父线程传递的ThreadLocalMap
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    private long stackSize;
    private long nativeParkEventPointer;

    /*
     * Thread ID
     */
    private long tid;

    private static long threadSeqNumber;


    private volatile int threadStatus = 0;


    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }


    volatile Object parkBlocker;

   
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();

    void blockedOn(Interruptible b) {
        synchronized (blockerLock) {
            blocker = b;
        }
    }

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

Thread的构造方法

java 复制代码
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    init(null, null, name, 0);
}

public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}


public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}


public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}
// inheritThreadLocals这个属性默认为true
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    // 这里会获取到创建当前线程所在的线程组
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
            g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
       explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();
    // 设置线程组
    this.group = g;
    // 设置是否守护线程,默认跟父线程状态一致
    this.daemon = parent.isDaemon();
    // 设置优先级以及上下文类加载器
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    // 因为inheritThreadLocals是默认true的,所以当父线程的inheritableThreadLocals存在的时候赋值给我们的子线程,于是完成了父子线程之间值的传递
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /**
     * 这个方法是ThreadLocalMap里面的方法,是创建一个拥有相同值但是新的ThreadLocalMap对象,可以看成是深拷贝的过程
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

     */
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}
  1. 关于ThreadGroup的解析可以去看Yarrow-Y大佬的解析
  2. 讲一下这边线程创建的流程。
  3. 创建线程的参数->构造函数->init方法->init方法里面对线程的成员变量进行赋值->设置必要属性->如果父线程的inheritableThreadLocals不为空那么传递给子线程。
  4. 以上就是Thread类中和ThreadLocal相关的信息。

ThreadLocal详解

ThreadLocal的成员变量和构造函数

ThreadLocal的成员变量

java 复制代码
public class ThreadLocal<T> {
    
    private final int threadLocalHashCode = nextHashCode();

  
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

 
    private static final int HASH_INCREMENT = 0x61c88647;

    // 
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
}
  • ThreadLocal的成员变量是非常少的,因为承载数据的对象是它里面的内部类ThreadLocalMap。
  • 那么这里的threadLocalHashCode就是每个对象被加入的时候会调用nextHashCode()方法去获得一个新的值,这里是由于静态变量的值是所有对象共享的,所以每次获取会看到新的值。
  • 为什么是每次增加0x61c88647这么大的数呢?
  • 因为这个值很特殊,它是 黄金分割数threadLocalHashCode的增量为这个数字,可以让 threadLocalHashCode 分布非常均匀, 从而减少哈希冲突。

测试代码

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by IntelliJ IDEA.
 * User: pzj
 * Date: 2023/11/11
 * Time: 21:13
 *
 * @author:pzj description: TestThreadLocalNextHashCode
 */
public class TestThreadLocalNextHashCode {
    static final int NEXT_HASH_CODE = 0x61c88647;
    static volatile AtomicInteger hashCode = new AtomicInteger(0);
    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            int code = nextHashCode();
            int pos = code & 15;
            System.out.println("Current pos is: " + pos);
        }

    }
    private static int nextHashCode(){
        return hashCode.getAndAdd(NEXT_HASH_CODE);
    }
}

运行结果如下:可以看到是十分均匀的,这块的话应该是数学原理,可以自己去查来看。

yaml 复制代码
Current pos is: 0
Current pos is: 7
Current pos is: 14
Current pos is: 5
Current pos is: 12
Current pos is: 3
Current pos is: 10
Current pos is: 1
Current pos is: 8
Current pos is: 15

这里ThreadLocal的构造函数就不介绍了,就是一个空构造函数,因为我们使用的多的是ThreadLocalMap。下面仔细介绍一下ThreadLocalMap

ThreadLocalMap

ThreadLocalMap的初见

看看Entry的整体设计

可以看到Entry是继承了弱引用对象的,事实上只有key是弱引用,可以看如下代码把ThreadLocal的key传递给父类。

java 复制代码
static class ThreadLocalMap {

    /* 这就是真正保存键值对的地方,可以看到是继承了WeakReference弱引用的*/
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    
    private static final int INITIAL_CAPACITY = 16;

    
    private Entry[] table;

    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

   
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    /**
     * Decrement i modulo len.
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

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

    // 根据父map进行深拷贝,把父线程的值传递到子线程中
    // 这里可能有疑问的childValue方法,因为此方法是在生成inheritedMap的时候调用的,并且inheritedMap重写了childValue方法,
    //protected T childValue(T parentValue) {return parentValue;} 清晰明了
    // 这个方法只在传递inheritableThreadLocals的时候调用,所以不用担心错误,不记得的话看上面thread类的init方法可以很清晰的知道。

    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
  • ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)这个方法只在两个地方进行了调用,如下图:
  • 要注意t.inheritableThreadLocals和t.threadLocals事实上都是ThreadLocalMap的对象。
  • 这两个调用的地方是它们俩分别的creatMap()方法。返回的就是ThreadLocalMap的对象。
  • 那么creatMap()是在哪里调用的呢?

ThreadLocal的方法介绍

java 复制代码
// 初始化值,事实上我们不会去调用
protected T initialValue() {
    return null;
}

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

public ThreadLocal() {
}

// 获取当前线程->获取ThreadLocalMap,为空的话就去setInitialValue
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();
}

// 看当前线程的map里面是否存在设置的值
boolean isPresent() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    return map != null && map.getEntry(this) != null;
}
// 这里如果当前ThreadLocalMap不存在的话会去创建
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}

// 这里如果当前ThreadLocalMap不存在的话会去创建
// 事实上就这两个set方法去调用了createMap方法
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 void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null) {
         m.remove(this);
     }
 }


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


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

// 这里是调用了第二个ThreadLocalMap的构造方法,就是进行深拷贝之后返回map
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}


T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}


static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue() {
        return supplier.get();
    }
}

接下来我们看一下父子线程传递的类。

InheritableThreadLocal

java 复制代码
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
  • 可以清楚的知道,如果父线程用的是InheritableThreadLocal的话,这里getMapcreateMap都会去赋值给对应的inheritableThreadLocals。
  • 还是要记得inheritableThreadLocalsthreadLocals 都是ThreadLocalMap 的对象,只是我们在创建的时候会有区别,使用的时候我们是通过自己创建的ThreadLocal和InheritableThreadLocals对象去获取值,所以并不会存在差异。
  • InheritableThreadLocals不过是提供了父子线程之间值的传递的功能。

ThreaLocal获取值

获取值是通过get方法->map.getEntry(this)

getEntry()方法介绍

java 复制代码
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 这里的e.get()方法实际上是顶层抽象类Reference的方法,就是返回当前对象的引用,也就是之前设置进去的threadLocal的key值,判断是否是自己设置的值,不是的话说明未命中。

    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}
  • 通过当前ThreadLocal对象初始化时候生成的threadLocalHashCode值去看是否在对应位置能找到值。
  • 未命中的原因是ThreadLocalMap是通过开放定址法中的线性探测法去解决hash冲突的。
  • 什么是线性探测法 ?可以简单的理解为当前位置被占用的话就往后找,next方法找到数组末尾的话会从头开始找位置,达成一个循环数组的功能。

getEntryAfterMiss 方法

java 复制代码
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        // 如果往后找的过程中碰见了被垃圾回收的空key值,进行一轮探测式清理,否则继续往后找
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

expungeStaleEntry方法

java 复制代码
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null; // 把值也置为null帮助gc
    tab[staleSlot] = null;// 然后把当前entry置为null 
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    // 获取下一个位置的entry,遇到下一个entry为空的时候结束
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 如果key为null,把值也设置为null帮助gc
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            // 如果当前的key不为空,并且没有放到正确的位置
            //把当前的位置置空,从h往后找到更近的能放的位置
            // 为什么能这么做,因为每次发现null结点才进行探测式清理,前面必然至少存在一个null结点
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    // 返回遇到null的位置
    return i;
}
  • 我们理解这个探测式清理就不进行画图了。
  • 首先,get的时候触发探测式清理 是因为寻找当前位置未命中未命中 有两种情况,gc强制回收了或者是线性探测 的时候往后找到位置放。比如 length == 16 的情况下, 3 & 15应该放在3号位置,但是3号位置当前有值,那么向后找,比如找到5的时候为空,存放当前的值,这个时候直接用3 & 15去找肯定也是未命中。
  • 在还没找到所需要的值的时候碰见key == null ,说明我们可以把值放到离正确位置更近的地方,比如4为null ,那么我们可以将上面放到第五个位置的值往前移动
  • 注意该方法返回了往后找到的第一个entry为null的位置。在启发式清理的时候使用这个值。
  • 以上就是get方法的一个流程。

ThreadLocal设置值

set方法

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

ThreadLocalMap的set方法

java 复制代码
private void set(ThreadLocal<?> key, Object value) {


    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    
    // e != null 才进行线性探测,否则直接设置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        // 同样,中途没有遇见null就往后找找到一个空位置存放,相等的话更新值
        if (k == key) {
            e.value = value;
            return;
        }

        // 替换Entry
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    // set完成会进行启发式清理和检查阈值,不满足条件的话需要进行rehash扩容操作。
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

replaceStaleEntry

java 复制代码
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // 先保存第一个位置,前面遇到的k为null的位置
    int slotToExpunge = staleSlot;
    // 向前找,找到从这个位置往前的的k为null的位置,保存下来找到的最靠前的位置,遇到entry为null停止
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    // 向后找,遇到entry为null的时候停止
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        // 遇到当前的key的话更新要设置的值
        if (k == key) {
            e.value = value;
            // 更新到更近的位置,tab[i]会在启发式清理的过程中被清理掉,
            // 因为传入的staleSlot的值的位置key为null,且在这个过程中没有遇到entry为null的
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // Start expunge at preceding stale entry if it exists
            // 如果上一个entry就为空,那么slotToExpunge不会被上面更新
            // 因为当前位置事实上被上面修改已经无效了,那么变成当前被设置的位置
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            // 进行启发式清理
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        // 同理
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // key没找到,直接更新到当前位置即可
    tab[staleSlot].value = null; // 帮助gc
    tab[staleSlot] = new Entry(key, value);

    // slotToExpunge被更新说明遇到k为null的情况了,进行启发式清理。
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

cleanSomeSlots启发式清理

返回值是代表至少清理了一个位置

java 复制代码
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            // 
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}
  • 传入的n == len, 并且我们知道len为2的次幂,假如len = 16。
  • 遇到k为null的时候进行探测式清理,并且会重置n == len = 16。
  • 如果没有遇到需要清理的情况,n无符号右移,也就是 n = n / 2,那么n进行五次寻找就会结束,否则一直清理。

rehash方法

如果set的时候启发式清理失败并且达到阈值,需要进行扩容操作。

java 复制代码
private void rehash() {
    // 再整体进行探测式清理,实在清理不了再去扩容
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

/**
 * Double the capacity of the table.
 */
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}

/**
 * Expunge all stale entries in the table.
 */
private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}

remove方法

请自行理解。

java 复制代码
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;
        }
    }
}

如何理解ThreadLocal的内存泄露问题

  • 我们可以看到堆中的ThreadLocal对象有两个引用 ,那么如果ThreadLocal不用弱引用作为key的话,堆中始终会存在指向ThreadLocal的强引用 ,久了就会造成内存的泄露

  • 所以如果在使用get方法 的时候由于栈上存在ThreadLocal的强引用 ,ThreadLocal是不会被回收的。

  • 但是用了弱引用之后,如果key被回收,那么value也就无法被回收了,所以ThreadLocalMap在get、set甚至rehash方法里面都是用了探测式、启发式 清理,但是这种清理是无法保证一定能全部清理完成的,因为即使是启发式,走到e == null的时候只会走2的次幂 ,假如是5次(length == 32),那么碰见5个null会停止。

  • 所以要求我们使用完成之后调用remove方法移除value,下一次GC的时候就会清理掉这个Entry对象了。

  • 并且要注意ThreadLocal的生命周期是和线程一致的,所以发生内存泄露主要是因为我们会使用线程池,线程池的核心线程是会一直存活的。

ThreadLocal的面试题

阅读本文以及JavaGuide的文章应该理解以下内容:

  • ThreadLocal 的key是弱引用 ,那么在 threadLocal.get()的时候,发生GC之后 ,key是否为null

  • ThreadLocalThreadLocalMap数据结构

  • ThreadLocalMapHash算法

  • ThreadLocalMapHash冲突如何解决?

  • ThreadLocalMap扩容机制?

  • ThreadLocalMap 中过期key的清理机制?探测式清理启发式清理流程?

  • ThreadLocalMap.set()方法实现原理?

  • ThreadLocalMap.get()方法实现原理?

  • 父子线程如何传递本地变量。

  • ThreadLocal的项目实战。

扩展

由于父子线程之间的传递是通过类似深拷贝的方式进行的,传递之后的修改是不能共享的,当然这也是一种保护的方法,如果要共享的话,可以参考线程间通信的方式,或者阿里开源的TransimmitableThreadLocal,有机会阅读源码的话一定再分享。

总结

本文仅为ThreadLocal理论方面源码的个人理解,如有错误敬请指出。

参考

  1. JDK11源码。
  2. [JavaGuide面试指南 ThreadLocal详解](ThreadLocal 详解 | JavaGuide(Java面试 + 学习指南))。
相关推荐
艾伦~耶格尔1 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20172 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
攸攸太上2 小时前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
罗曼蒂克在消亡3 小时前
graphql--快速了解graphql特点
后端·graphql
潘多编程3 小时前
Spring Boot与GraphQL:现代化API设计
spring boot·后端·graphql
大神薯条老师3 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析
2401_857622664 小时前
Spring Boot新闻推荐系统:性能优化策略
java·spring boot·后端
知否技术4 小时前
为什么nodejs成为后端开发者的新宠?
前端·后端·node.js
AskHarries5 小时前
如何优雅的处理NPE问题?
java·spring boot·后端
计算机学姐5 小时前
基于SpringBoot+Vue的高校运动会管理系统
java·vue.js·spring boot·后端·mysql·intellij-idea·mybatis