Java ThreadLocal 设计

ThreadLocal 是 JDK 中一个非常重要的工具,可以控制堆内存中的对象只能被指定线程访问,常见的应用就是事务管理。

ThreadLocal 使用

每一个 ThreadLocal 能够存放一个线程级别的变量,本身能够被多个线程共享使用,并且又能够达到线程安全的目的。

public final static ThreadLocal<String> res = new ThreadLocal<String>();

res 代表一个能够存放 String 类型的 ThreadLocal 对象。任何线程能够并发访问这个变量,进行设值、读取操作,都是线程安全的。

ThreadLocal 分析

构造方法

public class ThreadLocal<T> {

    // 静态变量
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    // 初始化成员变量
    private final int threadLocalHashCode = nextHashCode();

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    public ThreadLocal() {
    }
}

新创建的 ThreadLocal 使用 nextHashCode() 生成唯一 hashCode。

操作方法

ThreadLocal 最常见的操作就是 setgetremove

public class ThreadLocal<T> {

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

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

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
    }

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

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

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
}

可以看到 getMap() 是一个关键操作。首先获取当前线程t(Thread.currentThread();),使用t取到 ThreadLocalMap 对象 map。如果 map 不存在,需要创建,并赋值给 t.threadLocals(Thread 对象的成员变量)。如果 map 存在,调用 map 对象的 setgetremove 方法。

ThreadLocalMap

ThreadLocalMap 是 ThreadLocal 的一个内部类,同时也是 Thread 对象的成员变量。

ThreadLocalMap 的结构类似于 ArrayList,一个 Entry 数组 table。方法需要传递 ThreadLocal 对象,用于快速找到 Entry 的索引(一个 ThreadLocal 占用 Entry 数组 的一个索引位置)以及判断 Entry 中的对象是否属于该 ThreadLocal。

public class ThreadLocal<T> {

    static class ThreadLocalMap {

        // Entry 保存 ThreadLocal 和 v(value)
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        // 容量,用于扩容
        private static final int INITIAL_CAPACITY = 16;

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

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

        // ...
    }
}

ThreadLocal 中真正保存的值是在线程的 ThreadLocalMap 中(ThreadLocalMap 的生命周期与线程相同)。ThreadLocal 有可能出现内存泄漏的情况(ThreadLocalMap 弱引用的原因?),尽量调用 remove 方法,移除不需要的值。

ThreadLocal 应用

尽量不要用 ThreadLocal 当作传递参数的媒介

ThreadLocal 是全局的,参数放在 ThreadLocal,能够轻松解决很多问题。但是如果系统中多个地方对 ThreaLocal 进行设值、取值操作,代码混乱不可控的。

即使要用 ThreadLocal,也不是加一个每个参数就加一个 ThreadLocal 变量。可以将一些参数封装对象后再使用。

Spring 事务管理器

Spring 事务管理器通过 AOP 切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象。

假如事务管理器是 DataSourceTransactionManager,就会从 DataSource 中获取一个连接对象,通过一定的包装后将其保存在 ThreadLocal 中。Spring 也将 DataSource 进行了包装,重写了 getConnection() 方法,让线程内多次获取到的 Connection 对象是同一个。

为什么要放在 ThreadLocal 里

Spring AOP 并不能向应用程序传递參数。应用程序的每一个业务代码是事先定义好的,Spring 并不会要求在业务代码的入口参数中必须编写 Connection 的入口參数。因此选择了 ThreadLocal,通过它保证连接对象始终在线程内部,不论什么时候都能拿到,此时 Spring 很清楚什么时候回收这个连接,也很清楚什么时候从 ThreadLocal 中删除这个元素。

从 Spring 事务管理器的设计上能够看出。Spring 利用 ThreadLocal 得到了一个非常完美的设计思路,同时在设计时十分清楚 ThreadLocal 中元素应该在什么时候删除。


References:

https://www.cnblogs.com/yxysuanfa/p/7125761.html

https://jsbintask.cn/2019/04/01/jdk/jdk8-threadlocal/

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注

17 − 12 =