ThreadLocal的使用和一些注意点

82 阅读2分钟

简单理解ThreadLocal: 可以操作对线程自身的ThreadLocal.ThreadLocalMap,存放属于每个线程自己的内容,线程之间相互隔离。

本质: 每个Thread对象都有一个属性ThreadLocalMap,key为线程对象本身,它由ThreadLocal维护。Thread线程可拥有多个ThreadLocal维护多个自己的线程独享的值。

场景: 用于在多线程中为每个线程提供一个独立的变量副本,可避免线程安全问题,也用作需要一个线程内上下文传递的对象,如用作当前用户信息存储。

ThreadLocal部分源码:

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的Map(每个Thread都有一个ThreadLocal.ThreadLocalMap)
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //map的key为一个ThreadLocal对象,value是存的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取线程的map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //map的key为一个ThreadLocal对象
            map.set(this, value);
        else
            createMap(t, value);
    }

注意:

  • 使用ThreadLocal要特别注意保证每次使用完释放掉,不然会有内存泄漏的风险。
  • 线程池干扰,我们知道Java Web中每个请求都是一个独立的线程,但是为了提高性能,Web容器如Tomcat一般都会配置线程池提高效率,核心线程是共享的,若ThreadLocal没有正确的释放以便垃圾回收,那么不同请求使用了同一个常驻线程就会导致数据混乱
  • 线程继承问题:ThreadLocal是不支持继承的,子线程无法获取到父线程的ThreadLocal,要想子线程访问父线程的ThreadLocal可以用InheritableThreadLocal InheritableThreadLocal继承自ThreadLocal。那么是如何做到的呢?看Thread的构造函数
private Thread(ThreadGroup g, Runnable target, String name,
               long stackSize, AccessControlContext acc,
               boolean inheritThreadLocals) {
    //... 省略部分代码
    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);
    //子线程inheritableThreadLocals赋值
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    this.tid = nextThreadID();
}

InheritableThreadLocal部分源码:

//JDK 11.0.9
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);
    }
}