ThreadLocal 并不是一个线程,它是线程内部的数据存储类,它的数据不是共享的,只有在当前线程才能获取到,在当前线程存储的数据,意思就是说,A,B两个线程,在A线程把数据存储进ThreadLocal,只有在A线程才能取出,B线程取不到。
举个例子
    public static void main(String[] args) throws Exception {
        sThreadLocal = new ThreadLocal<>();
        sThreadLocal.set("主线程");
        new Thread("A"){
            @Override
            public void run() {
                super.run();
                sThreadLocal.set("A线程");
                System.out.print("A --- "+sThreadLocal.get()+"\n");
            }
        }.start();
        new Thread("B"){
            @Override
            public void run() {
                super.run();
                System.out.print("B --- "+sThreadLocal.get()+"\n");
            }
        }.start();
        System.out.print("main --- "+sThreadLocal.get()+"\n");
    }
打印的日志如下:
A --- A线程
main --- 主线程
B --- null
是不是很奇妙,虽然大家公用一个ThreadLocal对象,但是取出的数据却完全不一样,这是因为通过get方法,ThreadLocal会先取出一个ThreadLocalMap.Entry对象,再从Entry中获取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();
    }
     static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
但是怎么确保当前线程的Entry对象和另外线程的Entry对象不同呢?
先看ThreadLoacl的内部类ThreadLocalMap
成员变量
static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            // 
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        /*
         * 初始容量
         *必须是2的幂。
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * 存储Entry的数组
         * table的大小是2的幂次方
         */
        private Entry[] table;
        /**
         * 表中的项数。
         */
        private int size = 0;
        /**
         * 加载因子。也就是当容量达到多少需要扩充
         */
        private int threshold; // Default to 0
        /**
         *大于长度的2/3就会扩充
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
}
Entry对象是ThreadLocalMap的内部类,同时继承了WeakReference
构造方法
  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 ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
构造方法有两个。
第一个构造方法:创建了一个默认大小容量的数组,然后根据ThreaLocal的哈希值来做为table的角标存储第一个数据,同时扩容容量。
第二个构造方法:传入
存储Entry对象
//用来遍历 0,1,2,3--->遍历顺序是 1,2,3,0
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //通过hashCode来找到当前ThreadLocal所在的位置
            int i = key.threadLocalHashCode & (len-1);
            //从当前位置出发,不停的向后移动一位,再数组不填满的情况下,会从当前位置一直遍历到最后一个元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果ThreadLocalHashCode值相同,说明是同一个线程里面的参数 就覆盖掉先前的值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果为null,因为entry是一个弱引用,说明被回收掉了
                if (k == null) {
                //替换掉陈腐的Entry 也就是把过期的给替换掉
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //获取到return后的i 新建一个Entry存进去
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //判断需不需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
这里面的逻辑很清晰,就是把ThreadLocal和值以键值对的形式存给Entry,再把Entry对象存入到数组中。最后再判断一下需不需要扩容。
  private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            int slotToExpunge = staleSlot;
            //从slotToExpunge开始反序循环 找到slotToExpunge之前最前面的失效元素的位置,如果没找到,
            // 就是当前的位置为失效条目
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
            //从当前失效元素的位置开始,正序遍历
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;
                    //把失效元素与当前交换一下位置,保证哈希表的顺序。
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
                    // 如果存在之前的陈旧元素,就开始删除
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
            //删除所有的过期元素
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
获取Entry对象
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);
}
这个方法比较简单,通过传入的ThreadLocal的hashCode直接计算i的值,从而获取对应位置的Entry。
到这里,大概对ThreadLocalMap这个对象有点了解了,回过头看ThreadLocal。
构造方法
public 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);
    }
进来,获取当前线程对象,通过getMap()获取ThreadLocalMap对象。
这个方法其实返回的是Thread里面的一个变量,通过变量进行存储,确保Thread对应唯一的ThreadLocalMap。
ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
判断map是否为null,如果不为空则直接调用ThreadLocalMap的set方法,上面我们也分析过了,会以类似键值对的形式存入进Entry[]数组中。
如果map为null,则调用createMap方法,该方法是new 了一个ThreadLocalMap 并把值赋值给了当前Thread对象中的threadLocals变量。
这样就成功的把ThreadLocalMap与当前Thread进行了绑定,所以才可以在不同的Thread中,获取到不同的数据。
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
获取方法 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();
    }
同样是先获取到ThradLocalMap对象,再调用其getEntry方法,把当前ThreadLocal对象做为key传进去,获取到对应的entry对象,再从entry获取到value。
如果没有成功找到对应的Entry,则会调用setInitialValue方法,返回默认值。判断ThreadLocalMap是否为空,不为空,把当前的threadLocal和默认值Value 存进Map的数组中,如果为null,则创建map。
 private T setInitialValue() {
        T value = initialValue();
        //获取value值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //存值进去
            map.set(this, value);
        else
            //创建map
            createMap(t, value);
        return value;
    }
//初始值 
 protected T initialValue() {
        return null;
    }
总结
大概捋一下几个类的关系,这里面又Thread、ThreadLocal、ThreadLocalMap、Entry四个对象。
其中 Thread 持有ThreadLocalMap对象做为成员变量。
ThreadLocalMap是ThreadLocal的内部类。
Entry是ThreadLocalMap的内部类,同时继承WeakReference
Entry有ThreadLocal和Object value两个成员变量,以类似键值对的形式保存在ThreadLocalMap中的Entry[]数组中,以当前的ThreadLocal做为key,Object做为value。
所以当,在一个线程A中调用set方法 存入数据,会先获取当前的Thread对象,检查该对象的ThreadLocalMap,接着把值和ThreadLocal保存到ThreadLocalMap的数组Entry[]中(通过hashCode确定在数组中的位置),取值的时候,通过ThreadLocal,来寻找对应数组中的Entry对象,从而获取到值。
而如果是在线程B中取值,当前的Thread对象中的ThreadLocalMap还为null,所以查不到在A线程存入的值。
