ThreadLocal学习笔记

2020-08-04  本文已影响0人  皮多堡

一、经典实用场景

二、示例代码

分析一下代码执行结果


/**
 * @author haopeng
 * @date 2020-07-31 11:05
 */
public class TheadLocalTest {
    //1.声明ThreadLocal变量
    private static ThreadLocal<String> localName = ThreadLocal.withInitial(() -> "张三");

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程第一次获取到的值为: " + localName.get());
        localName.set("李四");
        //创建子线程
        Thread thread = new Thread(() -> {
            System.out.println("内部线程获取到的的值为:" + localName.get());
        });
        thread.start();
        thread.join();
        System.out.println("主线程第二次获取到的值为: " + localName.get());
        localName.remove();

        //如果需要线程数据共享可以使用InheritableThreadLocal
        ThreadLocal<String> local = new InheritableThreadLocal<>();
        local.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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

通过分析以上set方法的源码可以看出是通过当前线程获取一个ThreadLocalMap,并最终将设置的值以键值对形式存储在此Map中。

继续查看getMap(t)源码

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

这里可以看到ThreadLocal.ThreadLocalMap是线程类Thread中的一个静态变量,这也就是其数据隔离的本质原因了-----> 每一个线程对象内部维护一个ThreadLocalMap,隔离的数据就是跟线程绑定的,而不同线程之间是不能共享的

查看ThreadLocalMap关键源码

static class ThreadLocalMap {
    //1 Entry对象
      static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    // 2.Entry数组
    private Entry[] table;
    
    //3. 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.

            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)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
}

通过源码可以看出:

四、内存泄漏

  1. 首先要明白,由于是线程绑定的实现数据隔离,自然醒到数据时存在占空间中的,其实不完全是,只能说每一个线程的ThreadLocalMap的引用是存在栈空间中的,实际上ThreadLocalMap也是被创建出来的,所以存在堆空间中的

  2. 由于存在堆空间中的,所以需要垃圾回收。Entry中的Key实现了弱引用,这就导致了,当这ThreadLocal对象使用之后,没有外部强引用时,下次gc时会被回收,而此时时间的value值没有被回收,这就是内存泄漏的原因

    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    1. 解决内存泄漏的办法就是使用完手动remove

最后,如果想使用线程之间共享的一个全局变量呢 ,可以使用InheritableThreadLocal他是ThreadLocal的子类

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

使用如下:

ThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("测试线程数据共享");
上一篇下一篇

猜你喜欢

热点阅读