ThreadLocal学习

ThreadLocal是什么

ThreadLocal作用是创建线程局部变量。通常情况下我们创建的变量可以被任意一个线程访问并修改,而 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问和修改,所以就不存在线程安全问题。

使用场景

  • 每个线程都需要有属于自己的实例数据(线程隔离)
  • 实例需要在线程中的多个方法共享,不希望使用传参的形式

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

demo示例

code

    // 初始化,并赋初始值:default
    public static final ThreadLocal<String> THREAD_LOCAL = ThreadLocal.withInitial(() -> "default");

    public static void main(String[] args) {
        log();

        // 主线程值设置为test
        THREAD_LOCAL.set("test");
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                log();
                // 线程值设置为threadName
                THREAD_LOCAL.set(Thread.currentThread().getName());
                log();
            }).start();
        }
        log();
    }

    /**
     * 打印日志:threadName: value
     */
    private static void log() {
        System.out.println(Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
    }

步骤说明

  1. 首先初始化静态变量 ThreadLocal
  2. 打印当前主线程THREAD_LOCAL值
  3. THREAD_LOCAL设置为test
  4. 开启两个线程,打印打印THREAD_LOCAL当前值,后赋值为threadName,再次打印
  5. 打印当前主线程THREAD_LOCAL值

程序输出

main: default
main: test
Thread-1: default
Thread-1: Thread-1
Thread-0: default
Thread-0: Thread-0

结论

可以看到threadLocal变量在赋初始值default后被主线程修改为test,但是子线程拿到的依然是default,子线程内部修改为threaName也不会相互影响。

ThreadLocal 创建的变量只能被当前线程访问和修改,其他线程无法访问(每个线程都有属于自己的实例变量)

原理

ThreadLocal与Thread的关系

首先说下 ThreadLocal 与 Thread 的关系,ThreadLocal 与 Thread 同包都在java.lang包下,Thread 内部维护了一个 default 修饰的 ThreadLocalMap 实例,ThreadLocal 的操作都是围绕着 threadLocals 来操作的

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

threadLocal.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() {
    // 1.首先获取当前线程
    Thread t = Thread.currentThread();
    // 2.获取当前线程内部的ThreadLocalMap变量
    // t.threadLocals;
    ThreadLocalMap map = getMap(t);
    // 3.判断map是否为null
    if (map != null) {
        // 4.使用当前threadLocal变量获取entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 5.判断entry是否为null
        if (e != null) {
            // 6.返回Entry.value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 7.如果map/entry为null设置初始值
    return setInitialValue();
}

/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {
    // 1.初始化value,如果重写就用重写后的,默认null
    T value = initialValue();
    // 2.获取当前线程
    Thread t = Thread.currentThread();
    // 3.获取当前线程内部的ThreadLocalMap变量
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 4.不为null就set
        // key: threadLocal
        // value: value
        map.set(this, value);
    else
        // 5.为null就创建ThreadLocalMap对象
        createMap(t, value);
    return 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
 */
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) {
    // 1.初始化entry数组,size: 16
    table = new Entry[INITIAL_CAPACITY];
    // 2.计算value的index
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 3.在对应index位置赋值
    table[i] = new Entry(firstKey, firstValue);
    // 4.entry size
    size = 1;
    // 5.设置threshold: threshold = len * 2 / 3;
    setThreshold(INITIAL_CAPACITY);
}

threadLocal.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) {
        // 1.获取当前线程
        Thread t = Thread.currentThread();
        // 2.获取当前线程内部的ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 3.设置value
            map.set(this, value);
        else
            // 4.创建ThreadLocalMap(如上)
            createMap(t, value);
    }

threadLocalMap.set()

/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    // 1.别名table
    Entry[] tab = table;
    int len = tab.length;
    // 2.获取value的index
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         // 3.计算下一个index的位置
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        // 4.如果已经存在threadLocal就覆盖
        if (k == key) {
            e.value = value;
            return;
        }

        // 5.如果为null就,通过 replaceStaleEntry 方法将所有键为 null 
        // 的 Entry 的值设置为 null,从而使得该值可被回收
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 6.如果不存在threadLocal,则赋值
    tab[i] = new Entry(key, value);

    // 7.size++
    int sz = ++size;

    // 8.通过 expungeStaleEntry 方法将键和值为 null
    // 的 Entry 设置为 null 从而使得该 Entry 可被回收;
    // 判断是否需要resize:size >= threshold * 3/4
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

 /**
 * Re-pack and/or re-size the table. First scan the entire
 * table removing stale entries. If this doesn't sufficiently
 * shrink the size of the table, double the table size.
 */
private void rehash() {
    // 1.清理键和值为 null 的Entry
    expungeStaleEntries();

    // 2.大于等于阈值的3/4就resize,避免遍历次数增加导致耗时变长
    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

内存泄漏

由于每个线程访问某 ThreadLocal 变量后,都会在自己的 Map 内维护该 ThreadLocal 变量与具体实例的映射,如果不删除这些引用(映射),则这些 ThreadLocal 不能被回收,可能会造成内存泄漏。

参考:http://www.jasongj.com/java/threadlocal/

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

    /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

防止ThreadLocal内存泄漏

这里的 Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供,ThreadLocalMap 的每个 Entry 都是一个对 key 的弱引用,这一点从 super(k) 可看出。另外,每个 Entry 都包含了一个对 value 的强引用。

使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免上述 ThreadLocal 不能被回收而造成的内存泄漏的问题。

但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。

注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对键的弱引用),而非具体实例的的弱引用,所以无法避免具体实例相关的内存泄漏。

防止实例内存泄漏

对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏。

针对该问题,ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值