ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

ThreadLocal源码解读

2022-03-25 16:04:30  阅读:173  来源: 互联网

标签:set ThreadLocalMap 解读 ThreadLocal 源码 线程 数组 Entry


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

运行结果如下:

标签:set,ThreadLocalMap,解读,ThreadLocal,源码,线程,数组,Entry
来源: https://www.cnblogs.com/zhaodalei/p/16055059.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有