ThreadLocal-CSDN博客

71 阅读1分钟

ThreadLocal

使用

使用很简单,例子如下:

public class TestThreadLocal {
    static ThreadLocal<Integer> THREADLOCAL = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                THREADLOCAL.set(0);
                // 打印为0
                System.out.println("0号线程:" + THREADLOCAL.get());
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 打印为空
                System.out.println("1号线程:" + THREADLOCAL.get());
            }
        });
        t1.start();
        t1.join(); // 让主线程不要那么快的运行后边的
        t2.start();
    }
}

ThreadLocal提供了线程内存储变量的能力,且每一个线程读取的变量是对应的互相独立的。(线程隔离)

原理

从set方法入手,我们可以看到它的数据都是保存在一个ThreadLocalMap 当中的。

 /**
     * 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 map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocalMap 提供了一个Entry的内部静态类,并继承了WeakReference,实现key值的弱引用。

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

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

具体的set过程,数据最终被封装成一个Entry对象,并保存在一个Entry类型的对象数组当中,索引值为当前线程的哈希码和Entry表(长度-1)的与(&)结果。当然了,如果该位置已经被其他线程占据,会通过nextIndex方法寻找下一个位置。

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

场景

参数传递

  • 之前项目里头遇到过参数值需要一个一个从HttpServletRequest拿出的,而且多个类要用到(Controller层和Service层)。此时可以通过过滤器或者拦截器的方式,在过滤器或者拦截器里头统一处理,然后存放到ThreadLocal 中,后边的处理也可以直接从ThreadLocal 拿,因为是同一个线程。

注意

  • 使用之后,记得清理,因为在一些线程池场景中,由于线程复用的关系会存在数据错乱的场景。
  • 另外手动清理,也可以避免内存泄露的场景,从源码可以知道,在Entry中,key是弱引用,那么被弱引用关联的对象只能生存到下一次垃圾收集发生为止,可以被清楚。但value是强引用,这部分GC是不会清理的,积压过多,则有内存泄漏的风险。