专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

详解ThreadLocal

前言

本篇文章我来讨论一下什么是ThreadLocal以及它的实现原理。其底层数据结构有点类似HashMap,所以对HashMap不熟悉的朋友可以先去看一看我前面介绍HashMap的那篇文章。

本文如若有不对或不实之处,也欢迎各位读者朋友评论指正,欢迎探讨交流。

一、什么是ThreadLocal

我总结之后觉得可以这样理解:

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。

简而言之:ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是不可见的。

二、ThreadLocal实现的原理

我们来翻一翻ThreadLocal的源码实现,这是学习理解一个工具类最简单有效的方法。

ThreadLocal,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。

ThreadLocalMap,用来存储数据,采用类似hashmap机制, 存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。

1. ThreadLocal、ThreadLocal、Thread之间的关系

ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,Thread有ThreadLocal.ThreadLocalMap类型的属性。

2. ThreadLoalMap

从名字上看,可以猜到它也是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。

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

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

通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储

我们的值都是存储到这个Map上的,key是当前ThreadLocal对象!

如果该Map不存在,则初始化一个:

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

如果该Map存在,则从Thread中获取!

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

Thread维护了ThreadLocalMap变量

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null

从上面又可以看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中。于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是ThreadLocal对象本身,value则是要存储的对象。

在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。

51_1.png

这里需要注意的是,ThreadLoalMap和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。如果出现hash冲突怎么办?先来看看set方法。

3. set() 方法

首先,我们来看一下ThreadLocal的set()方法,因为我们一般使用都是new完对象,就往里边set对象了

public void set(T value) {
    // 得到当前线程对象
    Thread t = Thread.currentThread();
    // 这里获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 如果map存在,则将当前线程对象t作为key,要存储的对象
    //作为value存到map里面去
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //根据hash值计算存放下标i
    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对象的hash值,定位到table中的位置i,过程如下:

1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;

2、如果位置 i 已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;

3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;

这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置,可以发现,set和get如果冲突严重的话,效率很低。

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

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);
}
//查找对应key的 Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
} 

三、ThreadLocal原理总结

1、 每个Thread维护着一个ThreadLocalMap的引用
2、 ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
3、 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
4、 调用ThreadLocal的get()方法时,实际上就是在ThreadLocalMap获取值,key是ThreadLocal对象
5、 ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

正是上面的这些原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响。

四、总结

ThreadLocal设计的目的就是为了能够在当前线程中有属于自己的变量,实现不同线程间的数据隔离,线程间的数据互不干扰。ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。

文章永久链接:https://tech.souyunku.com/41173

未经允许不得转载:搜云库技术团队 » 详解ThreadLocal

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们