0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ThreadLocal基本内容与用法

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-13 11:39 次阅读

下面我们就来看看道哥都用的ThreadLocal。

1 ThreadLocal你来自哪里

Since: 1.2
Author: Josh Bloch and Doug Lea

又是并发大佬们的杰作,膜拜一下。怪不得道哥也爱用,自己设计的类总得用用。下面来看看基本内容与用法吧。

2 ThreadLocal原理

“This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).”

“此类提供了thread-local变量。这些变量不同于普通的类似变量,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自有的,独立初始化的变量副本,ThreadLocal实例通常是希望将状态与线程(例如,用户ID或事务ID)关联的类中的私有静态字段。”

通过老爷子们的描述,指北君大概也知道了ThreadLocal的推荐使用场景,

  1. ThreadLocal提供了一种访问某个特有变量的方法 访问到的变量属于当前线程,同一线程在任何地方都能访问同一个线程特有变量。
  2. 推荐定义为 private static 类型,但是Doug Lea老爷子在ThreadLocalRandom 和 ReentrantReadWriteLock 中使用了 private static final 类型。(肯定是当年写简介的时候手抖了)

2.1 Thread中如何存储

既然是线程的变量,自然是存在Thread对象中的一个变量了,但是它是通过ThreadLocal这个类来维护的。

//与此线程相关的ThreadLocal值,由ThreadLocal这个类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程相关的可继承的ThreadLocal值,由InheritableThreadLocal类来维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal中有一个内部类来ThreadLocalMap来维护这些线程本地变量,

static class ThreadLocalMap {
        //初始容量,2的n次方
        private static final int INITIAL_CAPACITY = 16; 
        
        //根据需要调整数组大小,2的n次方
        private Entry[] table;

        //上面Entry数组中的元素数量
        private int size = 0;

        //The next size value at which to resize  Default to 0
        private int threshold; 
}

ThreadLocalMap中的Entry结构如下,是一种key为弱引用(其目的就是Entry对象在GC时容易回收)的hash map,其中key总是ThreadLocal。

static class Entry extends WeakReference< ThreadLocal< ? >> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal< ? > k, Object v) {
        super(k);
        value = v;
    }
}

2.2 常用方法 get,set,remove 详解

  • get() 此方法是ThreadLocal最重要的方法之一,该方法返回此线程局部变量的当前线程副本中的值。大概可分为以下几步:
    (1) 先获取当前线程,然后再从线程中得到ThreadLocalMap。
    (2) 然后使用ThreadLocal对象的threadLocalHashCode进行散列计算,得到一个数组的index
    (3) 从Table数组中得到Entry,再对比Entry的key是不是和当前的ThreadLocal相等,如果相等就返回此Entry的value
    (4) 如果上一步中得到的Entry与当前ThreadLocal不相等,则会在方法getEntryAfterMiss中进行遍历Entry数组table中的每一个元素,如果找不到就返回null。而且在遍历的过程中会顺便清理一下废弃的Entry。

下面可以看一下get方法的具体代码。

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread(); 

    //从当前线程中获取ThreadLocalMap
    ThreadLocalMap map = getMap(t); 
    if (map != null) {
        
        //获取map中当前ThreadLocal对象对应的entry
        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) {
    //散列计算得到Entry中当前的index
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    
    //如果Entry不是null而且key等于当前
    // ThreadLocal对象则返回此Entry
    if (e != null && e.get() == key) 
        return e;
    else
        
        //Entry==null 或者其key不等于当前
        // ThreadLocal对象,遍历其余Entry
        return getEntryAfterMiss(key, i, e);
}

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)
            //如果遍历过程中发现有Entry的Key为Null,
            // 则清除掉作废的Entry
            expungeStaleEntry(i);
        else
            //计算Entry数组下一个index
            i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
}
  • set(T value) 此方法将此线程局部变量的当前线程副本中的值设置为指定值。

set线程本地变量步骤如下:
(1) 首先依然是获取此线程的ThreadLocalMap
(2) Map不为null时往map中插入数据,否侧创建map并插入数据
(3) 具体的set方法依然是先遍历Entry数组中所有的的Entry,然后依次对比每个Entry的key是否等于当前ThreadLocal,如果相等则直接替换现有Entry的value。如果Entry的Key为null,则立马清理废弃的Entry,并用新的Entry来替换此卡槽。
(4) 如果遍历完都没有return,则在在table中相应卡槽下新建Entry对象

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    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;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal< ? > k = e.get();
        
        //如果原Entry的key就是当前ThreadLocal对象,
        // 则直接替换现有value
        if (k == key) {     
            e.value = value;
            return;
        }
        if (k == null) {
            
    // 如果Entry的Key为null, 则直接替换为新的Entry            
            replaceStaleEntry(key, value, i); 
            return;
        }
    }
    // 如果前面的遍历没有return,
    // 则插入新的Entry对象到对应的卡槽    
    tab[i] = new Entry(key, value); 
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
  • remove() remove则相对简单,直接遍历ThreadLocalMap中Entry数组table,找到对应的Entry,将Entry的key置为null,然后再清理相应的Entry。
private void remove(ThreadLocal< ? > key) {
    ...
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //Entry 的key置为null
            e.clear();
            // 清理对应卡槽,
            expungeStaleEntry(i); 
            return;
        }
    }
}

3 Java中使用的ThreadLocal

Java中有哪些源码使用了ThreadLocal。

ThreadLocalRandom 中使用计算nextGaussian值时有使用到ThreadLocal。

InheritableThreadLocal继承了ThreadLocal,线程中使用inheritableThreadLocals这个map存储线程本地变量。和ThreadLocal的区别就是子线程依然可以访问到父线程的线程本地变量,实际应用中也推荐InheritableThreadLocal

ReentrantReadWriteLock中线程读写锁的计数器使用了ThreadLocal,其目的是记录每个线程获取读写锁的次数

static final class ThreadLocalHoldCounter 
        extends ThreadLocal< HoldCounter > {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}
//曾经的Doug Lea老爷子推荐static field,
// 而他默默的使用了static final。

4 如何使用ThreadLocal

ThreadLocal非常适合存储非线程安全的对象,并且不需要跨线程共享对象。很多需要线程隔离的操作都可以尝试使用它。

ThreadLocal也非常适合在Web应用程序中使用,典型的应用就是在Web请求进来一开始就将请求状态存储在ThreadLocal中,然后参与处理的任何组件均可访问该状态。

以下是一个ThreadLocal示例:

具体使用就是配合interceptor或者filter在线程刚开始执行的时候存储SessionContext,线程执行过程中可以随时访问该变量。然后在线程执行结束的时候再调用remove()方法移除,防止内存泄漏。

public class SessionContextHolder {
    private static final ThreadLocal< SessionContex > CONTEXHOLDER 
                    = new InheritableThreadLocal<  >();
    
    public static void remove(){CONTEXHOLDER.remove();};
    
    public static SessionContex get(){return CONTEXHOLDER.get();}
    
    public static void set(SessionContex sessionContex) {CONTEXHOLDER.set(sessionContex);}
}

总结

本文介绍了ThreadLocal的原理以及解析了常用方法的实现逻辑,以及在ThreadLocal一些应用。在一步步梳理的过程中,果然看到了以往忽略的各种细节,最后给出了一个小Case。并发编程大神道哥.李都在用的ThreadLocal,不妨在自己的项目中偷偷用上,保证丝滑舒适。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    12

    文章

    3862

    浏览量

    84672
  • 数组
    +关注

    关注

    1

    文章

    409

    浏览量

    25595
  • 线程
    +关注

    关注

    0

    文章

    489

    浏览量

    19500
收藏 人收藏

    评论

    相关推荐

    电机选型的基本内容有哪些 电机选型需要哪些参数

    电机选型需要的基本内容有:所驱动的负载类型、额定功率、额定电压、额定转速、其它条件。电机的种类有很多,在选电机的时候选一台好的电机非常重要,那么电机选型的详细步骤有哪些呢?下面我们来介绍一下电机选型
    发表于 08-05 10:29 9656次阅读

    ThreadLocal实例应用

    ThreadLocal相信大家都用过,但你知道他的原理吗,今天了不起带大家学习ThreadLocalThreadLocal是什么 在多线程编程中,经常会遇到需要在不同线程中共享数据的情况
    的头像 发表于 09-30 10:19 454次阅读
    <b class='flag-5'>ThreadLocal</b>实例应用

    ThreadLocal的定义、用法及优点

    ThreadLocal的定义、用法及其优点。 ThreadLocal是Java中一个用来实现线程封闭技术的类。它提供了一个本地线程变量,可以在多线程环境下使每个线程都拥有自己的变量副本。每个线程都可以独立地改变自己
    的头像 发表于 09-30 10:14 504次阅读
    <b class='flag-5'>ThreadLocal</b>的定义、<b class='flag-5'>用法</b>及优点

    USS通信协议的基本内容

    USS通信技术作为一种低成本的简单驱动控制技术,在工业现场有着广泛的应用。今天这篇文章,我们就和大家一起聊聊USS通信协议的基本内容
    发表于 01-19 06:45

    STM32单片机中需要用到的C语言知识有哪些

    STM32单片机中需要用到的C语言知识一、基本内容二、疑问点1.声明变量2.预处理一、基本内容二、疑问点1.声明变量const:可创建全局常量 局部常量, 数字常量, 数组常量 结构常量. 用法
    发表于 07-15 09:24

    课程实验指导书格式,具体项目指导书格式与基本内容要求

    附:1.课程实验指导书封面格式2.课程实验指导书前言内容要求3.具体项目指导书格式与基本内容要求4.学生实验报告基本内容要求具体项目指导书格式与基本内容
    发表于 09-24 11:39 0次下载

    《电视技术》课程基本内容及要求(电子专业)

    《电视技术》课程基本内容及要求(电子专业)本课程教学时数为48课时,教学内容及课程要求如下1、广播电视的基本知识熟悉光电转换过程;电视扫描原理;掌握广播电
    发表于 03-12 09:31 17次下载

    电子线路CAA的基本内容及发展动态

    本文综述了电子线路计算机辅助分析(CAA)的基本内容和方法;提出了这一领域目前正在研究的有关课题。关键词:大规模网络分析;非线性网络分析;开关电容网络分析;计
    发表于 06-01 13:31 15次下载

    基尔霍夫定律的基本内容是什么?

    基尔霍夫定律的基本内容是什么?基尔霍夫定律是电路分析和计算的重要工具。依据它可以作出决定电路特性的电流方程式和电压方程式。基尔霍夫定律又分为第
    发表于 10-04 15:08 9239次阅读
    基尔霍夫定律的<b class='flag-5'>基本内容</b>是什么?

    电子测量的基本内容

    电子测量的基本内容 随着科学技术的不断发展,测量的内容越来越多。电参数的测量分为电磁测量和电子测量两类,前者着重研究交
    发表于 07-11 15:23 2932次阅读

    液压传动系统设计的基本内容和一般流程

    本问介绍了液压传动系统的形式及禁忌、详细的介绍了液压传动系统设计的基本内容和一般流程,最后介绍了液压控制禁忌及分支管路的功率分配问题。
    发表于 01-04 13:56 8516次阅读
    液压传动系统设计的<b class='flag-5'>基本内容</b>和一般流程

    AVR单片机教程之SPI的用法程序资料说明

    关于SPI的一些基本内容就不再在这说了,下面主要是一些实用的用法知识。SPI是全双工通信,即可以单工通信,又可以全双工通信。在单工通信和半双工通信,比较简单,就是主机(从机)发送数据,对方接受数据。
    发表于 10-23 18:57 5次下载
    AVR单片机教程之SPI的<b class='flag-5'>用法</b>程序资料说明

    ThreadLocal发生内存泄漏的原因

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。但是如果滥用 ThreadLocal
    的头像 发表于 05-05 16:23 3488次阅读

    如何使用ThreadLocal来避免内存泄漏

    本次给大家介绍重要的工具ThreadLocal。讲解内容如下,同时介绍什么场景下发生内存泄漏,如何复现内存泄漏,如何正确使用它来避免内存泄漏。 ThreadLocal是什么?有哪些用途
    的头像 发表于 08-20 09:29 3881次阅读
    如何使用<b class='flag-5'>ThreadLocal</b>来避免内存泄漏

    ThreadLocal源码解析及实战应用

    ThreadLocal 是一个关于创建线程局部变量的类。
    的头像 发表于 01-29 14:53 323次阅读