当前位置:首页 > 公众号精选 > 架构师社区
[导读]来自:冰河技术       前言 我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示。 对共享变量加锁虽然能够保证线程的安全,但是却增加了开

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

来自:冰河技术

     

前言

我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

对共享变量加锁虽然能够保证线程的安全,但是却增加了开发人员对锁的使用技能,如果锁使用不当,则会导致死锁的问题。而ThreadLocal能够做到在创建变量后,每个线程对变量访问时访问的是线程自己的本地变量

什么是ThreadLocal?

ThreadLocal是JDK提供的,支持线程本地变量。也就是说,如果我们创建了一个ThreadLocal变量,则访问这个变量的每个线程都会有这个变量的一个本地副本。如果多个线程同时对这个变量进行读写操作时,实际上操作的是线程自己本地内存中的变量,从而避免了线程安全的问题。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

ThreadLocal使用示例

例如,我们使用ThreadLocal保存并打印相关的变量信息,程序如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //创建第一个线程
        Thread threadA = new Thread(()->{
            threadLocal.set("ThreadA:" + Thread.currentThread().getName());
            System.out.println("线程A本地变量中的值为:" + threadLocal.get());
        });
        //创建第二个线程
        Thread threadB = new Thread(()->{
            threadLocal.set("ThreadB:" + Thread.currentThread().getName());
            System.out.println("线程B本地变量中的值为:" + threadLocal.get());
        });
        //启动线程A和线程B
        threadA.start();
        threadB.start();
    }
}

运行程序,打印的结果信息如下所示。

线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1

此时,我们为线程A增加删除ThreadLocal中的变量的操作,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //创建第一个线程
        Thread threadA = new Thread(()->{
            threadLocal.set("ThreadA:" + Thread.currentThread().getName());
            System.out.println("线程A本地变量中的值为:" + threadLocal.get());
            threadLocal.remove();
            System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());
        });
        //创建第二个线程
        Thread threadB = new Thread(()->{
            threadLocal.set("ThreadB:" + Thread.currentThread().getName());
            System.out.println("线程B本地变量中的值为:" + threadLocal.get());
            System.out.println("线程B没有删除本地变量:" + threadLocal.get());
        });
        //启动线程A和线程B
        threadA.start();
        threadB.start();
    }
}

此时的运行结果如下所示。

线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1
线程B没有删除本地变量:ThreadB:Thread-1
线程A删除本地变量后ThreadLocal中的值为:null

通过上述程序我们可以看出,线程A和线程B存储在ThreadLocal中的变量互不干扰,线程A存储的变量只能由线程A访问,线程B存储的变量只能由线程B访问。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

ThreadLocal原理

首先,我们看下Thread类的源码,如下所示。

public class Thread implements Runnable {
    /***********省略N行代码*************/
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
     /***********省略N行代码*************/
}

由Thread类的源码可以看出,在ThreadLocal类中存在成员变量threadLocals和inheritableThreadLocals,这两个成员变量都是ThreadLocalMap类型的变量,而且二者的初始值都为null。只有当前线程第一次调用ThreadLocal的set()方法或者get()方法时才会实例化变量。

这里需要注意的是:每个线程的本地变量不是存放在ThreadLocal实例里面的,而是存放在调用线程的threadLocals变量里面的。也就是说,调用ThreadLocal的set()方法存储的本地变量是存放在具体线程的内存空间中的,而ThreadLocal类只是提供了set()和get()方法来存储和读取本地变量的值,当调用ThreadLocal类的set()方法时,把要存储的值放入调用线程的threadLocals中存储起来,当调用ThreadLocal类的get()方法时,从当前线程的threadLocals变量中将存储的值取出来。

接下来,我们分析下ThreadLocal类的set()、get()和remove()方法的实现逻辑。

set()方法

set()方法的源代码如下所示。

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程为Key,获取ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    //获取的ThreadLocalMap对象不为空
    if (map != null)
        //设置value的值
        map.set(this, value);
    else
        //获取的ThreadLocalMap对象为空,创建Thread类中的threadLocals变量
        createMap(t, value);
}

在set()方法中,首先获取调用set()方法的线程,接下来,使用当前线程作为Key调用getMap(t)方法来获取ThreadLocalMap对象,getMap(Thread t)的方法源码如下所示。

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

可以看到,getMap(Thread t)方法获取的是线程变量自身的threadLocals成员变量。

在set()方法中,如果调用getMap(t)方法返回的对象不为空,则把value值设置到Thread类的threadLocals成员变量中,而传递的key为当前ThreadLocal的this对象,value就是通过set()方法传递的值。

如果调用getMap(t)方法返回的对象为空,则程序调用createMap(t, value)方法来实例化Thread类的threadLocals成员变量。

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

也就是创建当前线程的threadLocals变量。

get()方法

get()方法的源代码如下所示。

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的threadLocals成员变量
    ThreadLocalMap map = getMap(t);
    //获取的threadLocals变量不为空
    if (map != null) {
        //返回本地变量对应的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //初始化threadLocals成员变量的值
    return setInitialValue();
}

通过当前线程来获取threadLocals成员变量,如果threadLocals成员变量不为空,则直接返回当前线程绑定的本地变量,否则调用setInitialValue()方法初始化threadLocals成员变量的值。

private T setInitialValue() {
    //调用初始化Value的方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    //根据当前线程获取threadLocals成员变量
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //threadLocals不为空,则设置value值
        map.set(this, value);
    else
        //threadLocals为空,创建threadLocals变量
        createMap(t, value);
    return value;
}

其中,initialValue()方法的源码如下所示。

protected T initialValue() {
    return null;
}

通过initialValue()方法的源码可以看出,这个方法可以由子类覆写,在ThreadLocal类中,这个方法直接返回null。

remove()方法

remove()方法的源代码如下所示。

public void remove() {
    //根据当前线程获取threadLocals成员变量
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        //threadLocals成员变量不为空,则移除value值
        m.remove(this);
}

remove()方法的实现比较简单,首先根据当前线程获取threadLocals成员变量,不为空,则直接移除value的值。

注意:如果调用线程一致不终止,则本地变量会一直存放在调用线程的threadLocals成员变量中,所以,如果不需要使用本地变量时,可以通过调用ThreadLocal的remove()方法,将本地变量从当前线程的threadLocals成员变量中删除,以免出现内存溢出的问题。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

ThreadLocal变量不具有传递性

使用ThreadLocal存储本地变量不具有传递性,也就是说,同一个ThreadLocal在父线程中设置值后,在子线程中是无法获取到这个值的,这个现象说明ThreadLocal中存储的本地变量不具有传递性。

接下来,我们来看一段代码,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args){
        //在主线程中设置值
        threadLocal.set("ThreadLocalTest");
        //在子线程中获取值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程获取值:" + threadLocal.get());
            }
        });
        //启动子线程
        thread.start();
        //在主线程中获取值
        System.out.println("主线程获取值:" + threadLocal.get());
    }
}

运行这段代码输出的结果信息如下所示。

主线程获取值:ThreadLocalTest
子线程获取值:null

通过上述程序,我们可以看出在主线程中向ThreadLocal设置值后,在子线程中是无法获取到这个值的。那有没有办法在子线程中获取到主线程设置的值呢?此时,我们可以使用InheritableThreadLocal来解决这个问题。

InheritableThreadLocal使用示例

InheritableThreadLocal类继承自ThreadLocal类,它能够让子线程访问到在父线程中设置的本地变量的值,例如,我们将ThreadLocalTest类中的threadLocal静态变量改写成InheritableThreadLocal类的实例,如下所示。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();

    public static void main(String[] args){
        //在主线程中设置值
        threadLocal.set("ThreadLocalTest");
        //在子线程中获取值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程获取值:" + threadLocal.get());
            }
        });
        //启动子线程
        thread.start();
        //在主线程中获取值
        System.out.println("主线程获取值:" + threadLocal.get());
    }
}

此时,运行程序输出的结果信息如下所示。

主线程获取值:ThreadLocalTest
子线程获取值:ThreadLocalTest

可以看到,使用InheritableThreadLocal类存储本地变量时,子线程能够获取到父线程中设置的本地变量。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

InheritableThreadLocal原理

首先,我们来看下InheritableThreadLocal类的源码,如下所示。

public class InheritableThreadLocal<Textends ThreadLocal<T{
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

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

由InheritableThreadLocal类的源代码可知,InheritableThreadLocal类继承自ThreadLocal类,并且重写了ThreadLocal类的childValue()方法、getMap()方法和createMap()方法。也就是说,当调用ThreadLocal的set()方法时,创建的是当前Thread线程的inheritableThreadLocals成员变量而不再是threadLocals成员变量。

这里,我们需要思考一个问题:InheritableThreadLocal类的childValue()方法是何时被调用的呢?这就需要我们来看下Thread类的构造方法了,如下所示。

public Thread() {
     init(nullnull"Thread-" + nextThreadNum(), 0);
 }

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}

public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

public Thread(String name) {
    init(nullnull, name, 0);
}

public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize)
 
{
    init(group, target, name, stackSize);
}

可以看到,Thread类的构造方法最终调用的是init()方法,那我们就来看下init()方法,如下所示。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)
 
{
       /************省略部分源码************/
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

可以看到,在init()方法中会判断传递的inheritThreadLocals变量是否为true,同时父线程中的inheritableThreadLocals是否为null,如果传递的inheritThreadLocals变量为true,同时,父线程中的inheritableThreadLocals不为null,则调用ThreadLocal类的createInheritedMap()方法。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

在createInheritedMap()中,使用父线程的inheritableThreadLocals变量作为参数创建新的ThreadLocalMap对象。然后在Thread类的init()方法中会将这个ThreadLocalMap对象赋值给子线程的inheritableThreadLocals成员变量。

接下来,我们来看看ThreadLocalMap的构造函数都干了啥,如下所示。

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) {
                //调用重写的childValue方法
                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++;
            }
        }
    }
}

在ThreadLocalMap的构造函数中,调用了InheritableThreadLocal类重写的childValue()方法。而InheritableThreadLocal类通过重写getMap()方法和createMap()方法,让本地变量保存到了Thread线程的inheritableThreadLocals变量中,线程通过InheritableThreadLocal类的set()方法和get()方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。此时,如果父线程创建子线程,在Thread类的构造函数中会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量中。

最后,附上并发编程需要掌握的核心技能知识图,祝大家在学习并发编程时,少走弯路。

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

长按订阅更多精彩▼

【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!

如有收获,点个在看,诚挚感谢

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭