ThreadLocal使用场景、源码分析及存在问题

ThreadLocal使用场景

并发编程中存在两种情况:

1、所有线程共享一个变量,且任意一线程对该变量的执行结果的改变会影响其他线程,此种情况下需对该变量加锁,防止脏数据的产生。(例如:多窗口购买火车票,每个窗口的火车票剩余数量的改变会直接影响其他窗口)

2、所有线程用一个变量名,但各个线程对该变量结果的改变不影响其他线程,使改变的结果保持在当前线程内。例如我整个项目每个方法都会用到userId(用户ID),这样我每一个方法的入参都需要附带一个userId,由于每个用户对应不同的线程,且每个用户的userId不同,彼此隔离没有任何关系,这种情况适用ThreadLocal,可为每一个线程提供一个线程级别的全局变量副本。使用ThreadLocal后,不再需要方法体提供userId入参,而是在全局想哪里获取就哪里获取,增加了代码的灵活性,减少了冗余。

 

ThreadLocal理解

1、ThreadLocal不同于共享变量,其在多个线程中对应多个副本,且每个副本之间没有关联。

2、ThreadLocal不适用于线程同步,其出现的目的只是为了各个线程维护自己的变量。


ThreadLocal源码分析

jdk版本:1.8 

首先在打开ThreadLocal类,首部有这样一段注释来描述这个类,大概意思是:这个类提供线程级别的本地变量,这些变量通过访问get和set方法拥有自己独立的初始化变量的线程级别的副本。典型的使用方法是在类中定义私有静态(private static)成员变量。每一个线程都拥有其对本地线程副本的隐式引用,只要线程活着,该本地变量就可用;当这个线程生命结束时,它的所有副本将被垃圾回收(除非存在其他对这些副本的引用)。

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 * 
 * Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {***}

 首先看set方法

由于该本地变量要和线程绑定,常规做法是将他们捆绑放入Map中。

/**
     * 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
        ThreadLocalMap map = getMap(t);
        // 若map不存在,则在map中放入当前ThreadLocal和value;否则createMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 getMap方法通过当前线程获取ThreadLocalMap(ThreadLocalMap可以理解为一个Map,但实际上不是一个Map)

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

 ThreadLocalMap有一个Entry内部类,功能类似Map的Entry内部类,其本身只有一个属性value,但是其继承的父类的父类

    public abstract class Reference<T> {

中存在一个属性:

    private T referent;

因此Entry的构造器是两个变量,Entry相当于维护了key(ThreadLocal<?>) 和 value(Object)的键值对关系。

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;
            }
        }
}

而threadLocals是一个全局变量, 默认为null

ThreadLocal.ThreadLocalMap threadLocals = null;

当通过getMap方法获取到的threadLocals为null时,通过createMap为threadLocals赋值

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

 get方法

 get方法就是通过当前线程t把ThreadLocalMap中存储的对应的value拿出来。这种map的机制,保证了当前线程对应的value只被该线程使用。

/**
     * 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();
    }

因此

ThreadLocal捆绑当前线程和ThreadLocalMap,维护他们之间的关系,通过getMap(Thread t)方法获取ThreadLocalMap;

ThreadLocalMap存在内部类Entry,类似Map的内部类Entry,维护key(ThreadLocal<?>) 和 value(Object)的键值对关系;

ThreadLocal存在问题

问题一:

在没有使用线程池的情况下,线程退出时,系统会回调Thread类的exit方法进行一些清理工作,包括对threadLocals置null,置null的变量会被垃圾回收。

/**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

但如果使用线程池,处于一直存活状态的线程不会退出,也不会进行ThreadLocal的清理工作,而你恰好给ThreadLocal设置了比较大的对象,当你再次用到这个ThreadLocal时,你会给他重新赋值,而之前设置的对象不再有用,并且不会被销毁,当这样的对象堆积的越来越多,有可能造成内存泄露。

一个比较好的习惯是,在当前线程使用完ThreadLocal时主动调用ThreadLocal.remove()方法,防止内存泄露。

问题二:

ThreadLocal不能解决新开子线程的变量传递问题,即如果你在项目中新启了多线程去跑程序,这种情况你会突然发现之前可以获取到的ThreadLocal突然都不见了,父线程传递本地变量到子线程中还需要InheritableThreadLocal等其他方式去解决。

解决方法请看:https://blog.csdn.net/qq_26012495/article/details/104379137

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值