回顾ThreadLocal

ThreadLocal作为解决特定场景下并发的一种方案,在Spring等框架及面试中经常会被问到,它是Java必须要掌握的基础知识之一。

ThreadLocal类的作用是抽象线程内变量的抽象,这类对象只在线程生命周期内起作用,不会被其它线程访问修改,它可以减少线程内多个函数或组件之间传递信息的复杂度,并且这类变量不存在多线程并发访问问题从而时间性能更好。

一、ThreadLocal的实现原理

首先在线程抽象类Thread中有一个ThreadLocal.ThreadLocalMap类型的threadLocals引用,它指向的ThreadLocalMap类中有一个Entry[]数组,其中每个Entry对象key为ThreadLocal的实例对象,value值为ThreadLocal对象设置的值。其主要源码如下:

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

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

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

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

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

 ThreadLocal变量是在代码中定义,在代码执行时线程对象拿到自己ThreadLocalMap类型的成员变量的值(可看做一个Map),然后将ThreadLocal对象做为Key和ThreadLocal对象设置的对象值作为value组成的Entry对象加入进来。

由于ThreadLocal对象作为可以,所以必须要区分每个ThreadLocal的实例对象,为此ThreadLocal类定义中定义了如下成员:

private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
      return nextHashCode.getAndAdd(HASH_INCREMENT);
}

通过final int型的threadLocalHashCode成员区分不同的ThreadLocal对象,它在构造时通过nextHashCode()函数复制,而nextHashCode()又通过原子变量自增某个固定值来保证线程安全。

注意ThreadLocalMap并没有实现Map接口,它内部是一个Entry数组,里面每个Entry对象保存一个key-value键值对,key是ThreadLocal对象,value是ThreadLocal对象set方法设置的对象值。即通过ThreadLocal的set方法把ThreadLocal对象自己当做key,参数值当做value,放进了ThreadLocalMap中。ThreadLocalMap的Entry与HashMap的Entry相比没有next字段,因此不存在链表的情况,出现hash冲突时就将元素放到数组中下一个位置,其插入一个key-value的实现如下:

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

二、ThreadLocal可能引起的内存泄漏

ThreadLocal对象间的引用关系图如下,虚线表示弱引用,如果ThreadLocal对象没有外部强引用指向它,在系统gc时就会被回收,此时ThreadLocalMap中就会出现key为null的Entry,而程序中也无法访问这些key为null的Entry,但如果当前线程不结束的话(尤其是在线程池线程复用一直不结束的场景下),这个key为null的Entry的value就会一直存在一个从GcRoots过来的强引用链:Thread Ref——》Thread——》ThreadLocalMap——》Entry——》value,无法被内存回收,因此造成内存泄漏,即在threadLocal设为null和线程结束前这段时间内Entry不会被回收,造成了内存泄漏。

解决方法,ThreadLocal对象在使用完后调用remove方法删除它。

JDK也建议将ThreadLocal变量定义为private static的,这样ThreadLocal对象的生命周期就长(一直存在ThreadLocal对象的强引用,所以它不会被回收),从而能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,在用完后调用remove方法删除它。

ThreadLocal

ThreadLocal使用注意

https://www.cnblogs.com/xzwblog/p/7227509.html

原文地址:https://www.cnblogs.com/doit8791/p/8723351.html