关于ThreadLocal

143 阅读3分钟

ThreadLocal是什么

java.lang.ThreadLocal是jdk提供的一个可以用来保存线程本地变量的类,它可以确保线程不会访问到属于其它线程的变量

ThreadLocal应用场景

1.线程隔离

常用案例是使用ThreadLocal为每个线程绑定一个会话、数据库连接等。比如通过ThreadLocal可以让每一个线程都使用自己的数据库连接,防止数据库连接混用造成的操作问题

2.在函数间传递参数

将需要的参数放入ThreadLocal,需要的时候再从ThreadLocal获取

ThreadLocal如何保证线程变量的隔离

每个线程实例中都会有一个ThreadLocalMap对象,线程本地变量其实就是保存在线程实例的ThreadLocalMap对象中的。因为每个线程都有自己的ThreadLocalMap,所以可以保证线程变量的隔离

通过ThreadLocal.set()方法设置线程本地变量就是在ThreadLocalMap中增加一个key-value条目,其中key是ThreadLocal对象的一个弱引用,value是设置的本地变量,下面是ThreadLocal类的setget操作的源码

public void set(T value) {
    Thread t = Thread.currentThread();
    //获取当前线程ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //ThreadLocalMap不为空则设置value,并且将this作为key
        map.set(this, value);
    } else {
        //ThreadLocalMap为空则创建Map,设置value
        createMap(t, value);
    }
}

public T get() {
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //从ThreadLocalMap对象中获取Entry条目
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap为什么要使用ThreadLocal对象的弱引用作为Key

使用弱引用是为了防止内存泄露问题,出现内存泄露问题需要满足以下条件:

  1. Entry条目使用强引用
  2. 线程不会死亡(现在大多使用线程池复用线程资源)
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.
     */
    //Entry类继承了WeakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            //这里表示Entry持有的是ThreadLocal对象的弱引用
            super(k);
            value = v;
        }
    }
}

考虑有以下代码

public void threadLocal() {
    //新建一个ThreadLocal,local是一个强引用
    ThreadLocal<String> local = new ThreadLocal<>();
    //设置一个值,此时当前线程的ThreadLocalMap对象中的Entry持有一个local指向对象的一个弱引用
    local.set("test");
    ...
}

当threadLocal方法执行完成后,local变量将被释放,此时local变量指向的对象只有一个引用,即当前线程的ThreadLocalMap对象中的Entry持有的弱引用。系统发生gc时,只有弱引用的对象将会被回收,此时Entry条目中的key就被回收了。因为在调用ThreadLocalset、get、remove方法时会清除key被回收的数据,所以不会造成内存泄露

现在使用ThreadLocal都会使用private static final修饰,这种情况下ThreadLocal会一直存在一个强引用,所以一定要记得调用remove方法