ThreadLocal

作用

ThreadLocal是本地线程变量,而ThreadLocal中寄存的是该线程的变量,且只能被本线程拜访。ThreadLocal 为每个线程都创立了变量正本,从而防止了并发拜访抵触。

上面让咱们来看一个例子:

public class ThreadLocalTest02 {    public static void main(String[] args) {        ThreadLocal<String> local = new ThreadLocal<>();        IntStream.range(0, 10).forEach(i -> new Thread(() -> {            local.set(Thread.currentThread().getName() + ":" + i);            System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());        }).start());    }}输入后果:线程:Thread-0,local:Thread-0:0线程:Thread-1,local:Thread-1:1线程:Thread-2,local:Thread-2:2线程:Thread-3,local:Thread-3:3线程:Thread-4,local:Thread-4:4线程:Thread-5,local:Thread-5:5线程:Thread-6,local:Thread-6:6线程:Thread-7,local:Thread-7:7线程:Thread-8,local:Thread-8:8线程:Thread-9,local:Thread-9:9

从后果能够看到,每一个线程都有本人的local 值,这就是TheadLocal的根本应用 。

上面咱们从源码的角度来剖析一下,ThreadLocal的工作原理。

ThreadLocal 源码剖析

1、set 办法

/** * Sets the current thread's copy of this thread-local variable * to the specified value.  Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of *        this thread-local. */public void set(T value) {    //首先获取以后线程对象    Thread t = Thread.currentThread();    //获取线程中变量 ThreadLocal.ThreadLocalMap    ThreadLocalMap map = getMap(t);    //如果不为空,    if (map != null)        map.set(this, value);    else        //如果为空,初始化该线程对象的map变量,其中key 为以后的threadlocal 变量        createMap(t, value);}/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map *///初始化线程外部变量 threadLocals ,key 为以后 threadlocalvoid createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);}/** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */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);}static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;    }}

其中ThreadLocalMapThreadLocal的一个动态外部类,外面定义了Entry来保留数据。而且是继承的弱援用。在Entry外部应用ThreadLocal作为key,应用咱们设置的value

作为value。对于每个线程外部有个ThreadLocal.ThreadLocalMap变量,存取值的时候,也是从这个容器中来获取。

2、get办法

/** * Returns the value in the current thread's copy of this * thread-local variable.  If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */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();}

通过下面的剖析,置信你对该办法曾经有所了解了,首先获取以后线程,而后通过key threadlocal 获取设置的value

ThreadLocal 内存泄露问题是怎么导致的?

ThreadLocalMap 中应用的 key 为 ThreadLocal 的弱援用,而 value 是强援用。所以,如果 ThreadLocal 没有被内部强援用的状况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会呈现 key 为 null 的 Entry。如果咱们不做任何措施的话,value 永远无奈被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中曾经思考了这种状况,在调用 set()get()remove() 办法的时候,会清理掉 key 为 null 的记录。应用完 ThreadLocal办法后最好手动调用remove()办法

static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;    }}

弱援用介绍:

如果一个对象只具备弱援用,那就相似于可有可无的生活用品。弱援用与软援用的区别在于:只具备弱援用的对象领有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具备弱援用的对象,不论以后内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程, 因而不肯定会很快发现那些只具备弱援用的对象。

弱援用能够和一个援用队列(ReferenceQueue)联结应用,如果弱援用所援用的对象被垃圾回收,Java 虚拟机就会把这个弱援用退出到与之关联的援用队列中。


著作权归Guide所有 原文链接:https://javaguide.cn/java/concurrent/java-concurrent-question...

参考文献:ThreadLocal,一篇文章就够了 - 知乎 (zhihu.com)