ThreadLocal源码分析

六眼飞鱼酱① 提交于 2020-10-29 00:47:38

前言

通过分析threadLocal的源码,来解答问题: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();
    }

实际上ThreadLocal中的值是存储在ThreadLocalMap这个类中的。继续进入getMap方法:

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

第一次执行get方法时,还没有ThreadLocalMap实例,进入setInitialValue方法:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

该方法就是设置ThreadLocal的初始值。此时ThreadLocalMap实例仍然不存在,进入createMap方法:

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

因此,可以看到,ThreadLocalMap实例是放在Thread实例的成员变量threadLocals中的。 ThreadLocalMap是ThreadLocal的静态内部类。进入ThreadLocalMap的构造方法:

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

其实这里的套路就跟Map比较像了。ThreadLocalMap内部维护了一个数组,数组中的元素是Entry对象。数组的下标就是当前threadLocal变量的hash值对数组长度取模。

获取threadLocal值的方法:

ThreadLocalMap.Entry e = map.getEntry(this);

就是传入当前ThreadLocal实例去获取的。ThreadLocalMap内部计算hash值,得到数组的索引,获取到对应的Entry对象返回。

Entry

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

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

注意entry对象弱引用了threadlocal对象。为什么这里要使用弱引用:

弱引用的意思就是,如果这个被弱引用的对象没有强引用了,那么这个对象就会在下一次GC的时候被回收掉,无论内存是否足够。

所以entry中弱引用了threadlocal的目的是:使得threadlocal在没有强引用之后就被回收掉,避免因为线程得不到销毁而threadlocal对象无法被回收。 就是把threadlocal的生命周期和线程生命周期给解绑了。(具体实现方式往后看set方法的实现)。
比如说,这个线程里面有很多个threadlocal对象,而且这个线程执行很久,前面的一些threadlocal对象其实已经没有被强引用了,那么使用弱引用就可以先把这个线程中没用的threadlocal对象回收掉。 如果entry没有弱引用threadlocal,直接放了对应的value,那么整个线程中所有的threadlocal的变量值都在线程被销毁的时候才统一回收。那么就是把threadlocal和线程的生命周期给绑定在一起了。

set方法

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

set方法也比较简单,就是以当前Threadlocal变量的hash值对数组长度取模作为数组下标,将value存入ThreadLocalMap中的数组里面的。

再继续进入ThreadLocalMap中的set方法:

private void set(ThreadLocal key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

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

    //需要不断循环,解决hash冲突(具体实现暂时不展开了)
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal k = e.get();

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

        //这里就体现了弱引用的好处了。这个数组位置的entry不为空,但k为空,说明这个值被垃圾回收了,那么这个位置就可以被新的threadlocal变量给替换了
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
	//cleanSomeSlots方法也是用来清除那些entry不为空,但其中的threadlocal变量已经被回收的对象。
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

threadLocal是如何实现线程隔离的

实现线程隔离也就是将ThreadLocalMap实例维护在每个线程实例的成员变量threadLocals中。 不同的线程去获取当前线程的threadLocal变量时,是通过Thread.currentThread()获取当前线程所维护的ThreadLocalMap实例的。因为Thread.currentThread()是线程隔离的,自然threadLocal也就是线程隔离的。

每个线程可以创建多个ThreadLocal实例来实现多个变量的线程隔离

为什么可以创建多个ThreadLocal实例?因为ThreadLocalMap其实就是一个Map,内部维护了一个table,table就是以每个ThreadLocal实例的hash值作为数组索引的。当然可以维护多个ThreadLocal。

注意

使用ThreadLocal一般都是声明在静态变量中。如果不断的创建ThreadLocal而没有调用remove方法,就会导致内存泄漏,特别是在高并发的web容器当中。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!