ThreadLocal 剖析

2,316 阅读2分钟

  1. ThreadLocal是什么?ThreadLocal有什么好处?
  2. ThreadLocal会产生内存泄漏吗?
  3. 当存储Value的时候发生冲突怎么办?
  4. 为什么在ThreadLocalMap中弱引用Entry呢?

  • 官方描述: 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

  • 自己的理解 ThreadLocal线程本地变量。为每个线程创建一个变量副本。 TODO

内部结构

  • ThreadLocal由ThreadLocalMap<ThreadLocal,Object>组成,key为ThreadLocal;
  • 本质上:ThreadLocalMap包含了一个Entry数组;

关键方法

1. ThreadLocal#set()

    public void set(T value) {
        Thread t = Thread.currentThread();
        //重要:根据线程获取Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

根据当前线程获取Map,如果Map为空,则createMap().

  • ThreadLocal#getMap()做了那些事情呢?
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;//返回一个**线程绑定**的ThreadLocalMap
    }
  • ThreadLocal#createMap()做了那些事情呢?
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

可以看出,其实Thread本身就内置了一个ThreadLocalMap。

那么接下来的重点就是看ThreadLocalMap如何初始化了。

1.1 创建ThreadLocalMap过程(初始化过程)

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //初始化一个长度=16的对象数组。Entry extends WeakReference
            table = new Entry[INITIAL_CAPACITY];
            //确定Hash值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //设值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    }

1.2 构造函数,创建Entry并设值

(重要!所以用截图)

image_1cnrgn0mi1ceqoch1gprpkv1i0738.png-100kB

拓展:

  1. 为什么在ThreadLocalMap中弱引用Entry呢?
  2. 但是只有Key是弱引用的,当发生下一次GC时,

1.3 如何设值呢?

麻烦点:如果Hash冲突了怎么办呢?

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

解决方法: ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

2. ThreadLocal#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();
    }

应用场景

数据库连接、Session管理

参考

  1. ThreadLocal-面试必问深度解析
  2. 【Java并发编程】深入分析ThreadLocal(八)