ThreadLocal是什么呢?简单来说,它是一个线程内部的存储类。
类组成:
1、无参构造方法
2、一个ThreadLocalMap静态内部类
3、ThreadLocalMap静态内部类里面存在一个Entry<ThreadLocal<?> k, Object v>[]数组
4、其他的就不细说,可以自己看源码,上面的三点是ThreadLocal在存取数据(set()和get())时,主要用到参数。
其中最重要的是set()和get()方法,下面来分析一下。
一、先分析下set()
public void set(T value) { //获取当前线程t Thread t = Thread.currentThread(); //然后调用getMap(t)方法见【1】 ThreadLocalMap map = getMap(t); //判断获取到的ThreadLocalMap是否为null if (map != null) //不为null见【3】 map.set(this, value); else //若为null见【2】 createMap(t, value); } //【1】返回的是当前线程的一个类变量ThreadLocalMap threadLocals ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //这个就是Thread的一个类变量,初始值为null ThreadLocal.ThreadLocalMap threadLocals = null; //【2】由上面知道ThreadLocalMap初始值为null,所以会调用createMap(t, value)方法, //这里是new了一个ThreadLocalMap实例,然后赋值给Thread类变量t.threadLocals void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //ThreadLocalMap构造方法,主要是创建了一个new Entry[]数组,INITIAL_CAPACITY的值为16 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); } //【3】ThreadLocalMap的set()方法,逻辑如下: private void set(ThreadLocal<?> key, Object value) { //获取的是的ThreadLocalMap类变量tab,由构造方法知道,table是一个长度为16的数组 Entry[] tab = table; int len = tab.length; //根据hash值计算数组下标 int i = key.threadLocalHashCode & (len - 1); //从计算的下标开始遍历整个Entry[] for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //存在当前ThreadLocal为键的数据,则覆盖掉对应的值 ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } //如果出现键为空的数据,则使用当前ThreadLocal为键覆盖掉数据(感觉是为了优化,键为空的数据,其实值已经是无用的了) if (k == null) { replaceStaleEntry(key, value, i); return; } } //如果遍历完后不满足,则new一个Entry并赋值 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) //size超过设置的阈值的3/4,则会扩充 rehash(); }
从上面可以知道,set()流程是:ThreadLocal.set()—>ThreadLocalMap.set()—>存入到Entry[]数组中,并且可以知道,
一个线程只会存在一个ThreadLocalMap,所以也只会存在一个Entry[]数组,Entry[]按不同的下标,存储了不同的ThreadLocal数据
所以一个线程可以存在多个ThreadLocal数据,以键值对的形式存储在同一个Entry[]数组,只是数组的下标不同。
那么一个ThreadLocal变量可以被不同的线程使用吗?答案是可以,这里的ThreadLocal仅仅指引用,不是指set()存储的实际值。
因为从上面知道,最终都是以键值对的形式存储在Entry[]中,ThreadLocal的引用为键,实际存储的数据为值。
(ThreadLocal本身不存储信息,它只是被作为键值对中的键,显然这个键在不同的Entry[]中是可以重复的,也就是可以被多个线程使用的)
下面用一幅图来更直观的说明一下
a、看线程1的红线和蓝线,ThreadLocal_1.set(),首先找到当前线程1,然后找到当前线程的ThreadLocalMap_1,然后通过引用哈希值按一定的规则计算数组下标 i,
然后以ThreadLocal_1引用为键,实际要存储的数据为值,存在Entry_1[ i ]位置
b、第二个ThreadLocal_2执行set()方法,和1中的步骤一样,不同点在于计算出的数组下标是 j,ThreadLocal_1和ThreadLocal_2的引用哈希值不同,
所以按相同的规则计算出来的下标是不同的,所以存在Entry_1[ j ]位置
上面的a和b说的是不同的ThreadLocal变量在同一个线程中的存储,下面说一下同一个ThreadLocal变量在不同的线程中的存储
c、看ThreadLocal_1的两条红线,和a一样,ThreadLocal_1存在线程1的Entry_1[ i ]位置
d、ThreadLocal_1.set(),在线程2中使用时,找到的是当前线程2,然后找到当前线程的ThreadLocalMap_2,然后通过引用哈希值按一定的规则计算数组下标 i,然后以ThreadLocal_1引用为键,实际要存储的数据为值,存在Entry_2[ i ]位置
这里可以发现,存储的Entry[]数组不同了,在c中是Entry_1[],在d中是Entry_2[],前面已经说过,每一个线程都有一个自己的Entry[]数组,但是他们在各自的数组中的下标是一样的,因为ThreadLocal引用是一样的,哈希值也是一样的,按相同的规则计算得到的下标也就是一样的
二、分析下get()
get()方法和set()是类似的,代码如下:
public T get() { Thread t = Thread.currentThread(); //获取当前线程的ThreadLocalMap变量 ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { //然后在Entry[]数组中获取以当前ThreadLocal引用为键的数据 ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //返回值 return result; } } //返回null return setInitialValue(); }
同样是获取到当前线程,然后获取到当前线程的ThreadLocalMap,然后获取到Entry[]数组,按ThreadLocal引用哈希值计算得到数组下标 i,然后获取到对应的Entry,返回里面的值。
总结:
1、ThreadLocal本身不存储实际信息,它只是被作为键值对中的键
2、一个线程可以存在多个ThreadLocal,以键值对的形式存储在同一个Entry[]数组,只是数组的下标不同
3、一个ThreadLocal可以被多个线程使用,以键值对的形式存储在不同的Entry[]数组中,数组下标相同,且Entry的键是一样的,存储的值按各个线程实际set的数据为准
三、下面介绍下ThreadLocal的简单用法
ThreadLocal在实际中如何使用?由上面的总结知道,一个ThreadLocal可以被多个线程使用的,我们只需要拿到这个ThreadLocal引用,然后调用get()方法,里面会自己去寻找各个线程的Entry[]数组,然后计算下标后取对应的值
所以可以创建一个中间类,然后新建一个ThreadLocal静态变量(静态变量只会存在一份),如果想要多个ThreadLocal可以在类中创建多个ThreadLocal,如下:
public class ThreadLocalTest { private static ThreadLocal<String> threadLocal; private static ThreadLocal<String> threadLocal1; public static Object getUserId() { return threadLocal1.get(); } public static void setUserId(String userId) { threadLocal1.set(userId); } public static Object getUserName() { return threadLocal.get(); } public static void setUserName(String userName) { threadLocal.set(userName); } static { threadLocal = new ThreadLocal<>(); threadLocal1 = new ThreadLocal<>(); } }
在代码中使用如下:
public class test { public void testThreadLocal() { ThreadLocalTest.setUserName("李四"); ThreadLocalTest.setUserId("lisi"); new Thread(() -> { ThreadLocalTest.setUserName("张三"); ThreadLocalTest.setUserId("zhangsan"); test(); }).start(); test(); } public void test() { String name = (String) ThreadLocalTest.getUserName(); String id = (String) ThreadLocalTest.getUserId(); System.out.println("name:" + name); System.out.println("id:" + id); } }
运行结果如下: