关于java:ThreadLocal

15次阅读

共计 3693 个字符,预计需要花费 10 分钟才能阅读完成。

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 为以后 threadlocal
void 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)

正文完
 0