【java】ThreadLocal应用以及理解

ThreadLocal使用

以线程为作用域,每个线程可以定义线程内独有的变量,不会对其它线程或者作用域造成影响。

如某线程调用了如下方法:

void doWork(User user){
    functionA(user);
    functionB(user);
    functionC(user);
    functionD(user);
}

线程中频繁地参数传递或者状态传递比较容易出现过度传参、状态不一致等问题。
在上面的doWork方法中,假如ABCD又需要一个新的参数,修改起来也很不方便。
这个时候就可以使用到ThreadLocal来在单个线程作用域内设置和保存一个变量的值或者保持一个状态。

使用ThreadLocal后上面的代码就可以改造成

static final ThreadLocal<User> sThreadLocal = new ThreadLocal<User>();

void doWork(User user){
    try{
        sThreadLocal.set(user);
        functionA();
        functionB();
        functionC();
        functionD();
    }...
    finally{
        //在确认某个变量在线程中不再使用时,需要remove防止内存泄漏
        sThreadLocal.remove();
    }
}

functionA(){
    User user = sThreadLocal.get();
    //do something
    //...
}

functionB(){...}
......

经典使用场景

spring的事务隔离级别控制,如何保证事务之间的独立性单独提交?这里就用到了ThreadLocal用来保证每个事务使用的是同一个数据库连接

TransactionSynchronizationManager类部分源码:

private  static  final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

private  static  final ThreadLocal<Map<Object, Object>> resources =  new  NamedThreadLocal<>("Transactional resources");

private  static  final ThreadLocal<Set<TransactionSynchronization>> synchronizations =  new  NamedThreadLocal<>("Transaction synchronizations");

private  static  final ThreadLocal<String> currentTransactionName =  new  NamedThreadLocal<>("Current transaction name");

此外还有session的数据隔离也用到了ThreadLocal。

ThreadLocal源码

ThreadLocal是以ThreadLocalMap来存储对象的,每个线程都有一个自己的ThreadLocalMap。取数据时,调用threadLocal的get方法,方法中分三步:

  1. 首先获取当前线程,用当前线程获取ThreadLocalMap对象
  2. 获取到该Map对象后,再用threadLocal本身作为key去获取Entry
  3. 获取该entry的value值

get源码如下:

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

set源码如下:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

内存泄漏

ThreadLocalMap是Thread的一个属性,被当前线程所引用,所以它的生命周期跟Thread一样长。

ThreadLocalMap是ThreadLocal的内部静态类,当一个线程使用到多个ThreadLocal对象时,对应的ThreadLocalMap则根据多个key去取value。

ThreadLocalMap对Entry的key使用了弱引用,如下:

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

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

弱引用可以在ThreadLocal使用完成时,由于Entry对ThreadLocal(key)为弱引用,所以ThreadLocal可以被正常回收,但仍有可能存在内存泄漏的问题。
除了threadLocal这个key,还有value是被Entry强引用的,如果entry没有被删除,那么就会造成value的内存泄漏。
这种内存泄漏有两个前提,同时符合这两个前提时,就有可能造成内存泄漏:

  1. 调用完成后没有手动删除这个Entry
  2. CurrentThread仍然运行

由于ThreadLocalMap的生命周期跟Thread一样长,于是导致了该Entry也一直存在于内存中,造成浪费。

因此使用ThreadLocal时,当一个值设置后不再使用,需要手动对其进行删除,调用ThreadLocal的remove方法即可。