ThreadLocal详解

ThreadLocal:线程局部变量。线程局部变量会将共享变量拷贝一份到当前线程中,保证了变量初始值的共享,线程之间变量的隔离,以及单个线程内的数据共享。

使用ThreadLocal

以JDBC Connection为例,每个dao都需要一个Connection对象用于进行数据库操作。因为Dao在Spring中是单例的,所以属性Connection可能会被多个线程访问,但是Connection并不是线程安全的。使用ThreadLocal可以很好的解决问题,每个线程在使用Connection时,拷贝一份Connection对象,各自使用各自的,从而避免了共享变量引发的线程安全问题:

public final class ConnectionUtil {

    private ConnectionUtil() {}

    private static final ThreadLocal<Connection> conn = new ThreadLocal<>();

    public static Connection getConn() {
        Connection con = conn.get();
        if (con == null) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                con = DriverManager.getConnection("url", "userName", "password");
                conn.set(con);
            } catch (ClassNotFoundException | SQLException e) {
                // ...
            }
        }
        return con;
    }
}

ThreadLocal源码分析

ThreadLocal主要拥有以下几个方法,下面从这几个方法作为切入点分析tl的源码:

ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("字符串"); // 初始化ThreadLocal
tl.get(); // 获取变量
tl.remove(); // 使用完毕,清除变量,否则有可能造成内存泄漏

先看set方法:

/*ThreadLocal*/
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 当前线程中的threadLocals,该对象是一个Map
    if (map != null)
        map.set(this, value); // 如果map不为空,那么就向map中添加一个key为threadLocal,value为变量值的元素
    else
        createMap(t, value); // 如果map不存在,则创建一个map对象
}

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

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

所以,在线程设置一个共享变量到ThreadLocal中时,会将变量设置到Thread对象的threadLocals(是一个map)属性中。

再来看get以及remove方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); // 获取指定key对应的Entry对象
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result; // 取到了立即返回
        }
    }
    return setInitialValue(); // 没取到初始化并返回null
}

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread()); // 从threadLocals中移除key为tl的元素
    if (m != null)
        m.remove(this);
}

所以,ThreadLocal变量本质上是将变量存储在当前线程Thread对象的thredLocals属性中的,此属性类型是ThreadLocal的内部类ThreadLocalMap,存放的变量key为threadLocal对象,set、get、remove本质上都是对这个map进行操作。

现在我们再看ThreadLocalMap类:

static class ThreadLocalMap {
   // map 存储的元素类型, 他继承自WeakReference,是一个弱引用
   static class Entry extends WeakReference<ThreadLocal<?>> {
       Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
   }
   // ThreadLocalMap实际上是一个Entry[]数组
   private Entry[] table;

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
       table = new Entry[INITIAL_CAPACITY];
       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
       table[i] = new Entry(firstKey, firstValue);
       size = 1;
       setThreshold(INITIAL_CAPACITY);
   }

}

ThreadLocal是如何保证数据隔离的

ThreadLocal的隔离特性完全依赖线程之间的不可见性。具体请参考JMM(Java内存模型)。

ThreadLocal内存泄漏

参考

最后更新于