详解ThreadLocal

什么是ThreadLocal?
ThreadLocal提供了线程独有的局部变量存储能力,可以在整个线程存活的过程中随时取用。

ThreadLocal是线程间隔离的?
ThreadLoca中线程的局部变量如何存储的?

ThreadLocal实现原理

Thread存储线程独有的变量(存储多个变量(值)时,可以创建多个ThreadLocal用来存储),以供在整个线程存活过程中取用。这种能力由ThreadLocal中的ThreadLocalMap实现。Thread在存储自己独有变量时,ThreadLocal实例在该Thread环境内使用set方法实现变量(值)存储。(继续探究原理)实际是静态内部类ThreadLocalMap将该变量(值)以key=当前线程的ThreadLocal实例,value=变量(值)的键值对对象Entry形式存储到了ThreadLocalMap下的数组中。

为什么一个ThreadLocal在同一线程中只能保存一个变量?在另一个线程可以再次保存一个变量?ThreadLocal和Thread是多对多关系。同Thread下,Thread和ThreadLocalMap是一对一关系。同Thread下,ThreadLocal和ThreadLocalMap是多对一关系。

ThreadLocal在保存变量时,是以当前ThreadLocal实例作为key,回答了第一个问题。而保存变量的"容器"是个数组。数组元素是(其实只是个封装的对象而已)Thread.currentThread().TreadLocalMap。当保存一变量时会判断当前线程下的TreadLocalMap是否为空,为空则会从新创建当前线程下的TreadLocalMap。因此回答了第二个问题

	
	/// 定义两个ThreadLocal、 开启两个线程进行验证 <Android API 30 Platform>
	
    private static ThreadLocal<String> local = new ThreadLocal<>();
    private static ThreadLocal<String> localNext = new ThreadLocal<>();
    public static void threadLocal() {
        new Thread(()->{
          	// local.set方法,在存放值时,其实是放到了此时local所在线程ThreadLocal静态内部类ThreadLocalMap中了。
            local.set("hello");
            System.out.println("获取线程一保存的值= "+ local.get());
            // 这行说明一个Thread中可以有多个ThreadLocal用来存储变量。
            localNext.set("hello-next");
            System.out.println("获取线程二保存的值= "+ localNext.get());
            // 这行代码又使用local.set做了不同变量值存储。结果是原先的值被替换。
            // 原因是,存储值具体实现其实是在ThreadLocalMap中。其内部实现使用Entry数组存储kye-value键值对的对象Entry。
            // 键值对对象存储,value=将要存储的变量(值),key=ThreadLocal实例
            // 在ThreadLocalMap内部存储过程,若存储的键值对对象entry已存在,且key相同则value值覆盖。否则,创建entry对象存储到数组中;
            // 因此,同一个local中存储变量(值)多次存储将会被覆盖,且存储的变量(值)只有一个。
            local.set("world");
            System.out.println("获取线程一重新保存后的值= "+ local.get());
        }).start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 而这里local.get,则会在此时local所在的当前线程中去获取ThreadLocal中存储的值。
            // 显然,这并没有做过任何值存储过程,当然local.get==null
            System.out.println("从新开的线程中获取上一个线程local存储的值= "+ local.get());
        }).start();
    }

/**结果*/
获取线程一保存的值= hello
获取线程二保存的值= hello-next
获取线程一重新保存后的值= world
从新开的线程中获取上一个线程存储的值= null

从上面执行结果可知,ThreadLocal是线程间隔离的。ThreadLocal中线程的局部变量又是如何存储的——借助ThreadLocal实现局部变量存储。
接下来进入源码看下这俩是如何实现的~

// Thread.java
public class Thread implements Runnable {
   .....
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocalMap 是 ThreadLocal的静态内部类

在Thead中有定义ThreadLocalMap静态内部类。并且在ThreadLocal中,在执行存储ThreadLocal.set (T value)时,先获取当前环境中的线程,并由线程获取ThreadLocalMap实例。若ThreadLocalMap为空,则会先去初始化(创建)。并将ThreadLocalMap实列赋值给Thread中定义的ThreadLocal.ThreadLocalMap成员变量(因此)。若ThreadLocalMap非空,则通过Thread拿到ThreadLocalMap的实例,然后将要存储的变量(值)存储到ThreadLocalMap实例中。

public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程,同Thread下,只有一个ThreadLocalMap
    ThreadLocalMap map = getMap(t); // 若ThreadLocalMap为空,则会先去初始化(创建)
    if (map != null)
        map.set(this, value);
    else
       createMap(t, value);
}

void createMap(Thread t, T firstValue) { // ThreadLocalMap实例为空,则会先去初始化(创建)
	// 赋值ThreadLocalMap实例给Thread成员变量 ThreadLocal.ThreadLocalMap threadLocals
    t.threadLocals = new ThreadLocalMap(this, firstValue); 
}

从上面描述看,每个新建的Thead实例,他们的成员变量Threadlocal必定是互相独立的。因此,ThreadLocal是线程间隔离的。

再看存储过程,由ThreadLocal的set (T value)方法执行存储,进而将存储交给ThreadLocalMap方法set(ThreadLocal<?> key, Object value)。在ThreadLocalMap方法内部维护了一个Entry[]数组,继而该数组会执行Thread局部变量的实际存储。存储方式,是通过当前ThreadLocal作为key并通过该key值执行int i = key.threadLocalHashCode & (len-1);获取变量将要存储在数组中的位置下标。

private void set(ThreadLocal<?> key, Object value) {
   Entry[] tab = table;
   int len = tab.length;
   int i = key.threadLocalHashCode & (len-1);
   // 在ThreadLocalMap内部存储过程,若存储的键值对对象entry已存在,且key相同则value值覆盖。
   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;
        }
      }
	 // 否则,创建entry对象存储到数组中;
     tab[i] = new Entry(key, value);
     int sz = ++size;
     if (!cleanSomeSlots(i, sz) && sz >= threshold)
     rehash();
}

获取到下标之后,会先判断下标位置是否存在节点Entry。若存在则比较Entry的key(即ThreadLocal)是否与将要存入的变量的key是否相同。相同则覆盖,不同则新建Entry并存储。

ThreadLocal是一种与线程绑定的变量,它可以解决多线程并发访问的问题。与Synchronized不同,ThreadLocal为每个线程提供了一个独立的变量副本,每个线程都可以独立地修改自己的副本,而不会影响其他线程的副本。 ThreadLocal的使用方法比较简单。我们可以通过ThreadLocal类的set()方法来设置当前线程所关联的变量的值,通过get()方法来获取当前线程所关联的变量的值。在使用完ThreadLocal后,如果不再需要这个变量,应该调用remove()方法来清除当前线程的关联变量,避免内存泄漏的问题。 ThreadLocal的原理是通过每个线程都拥有一个独立的ThreadLocalMap对象来实现的。ThreadLocalMap内部使用一个Entry数组来存储键值对,键为ThreadLocal对象,值为对应的变量副本。在获取当前线程所关联的变量时,会根据ThreadLocal对象找到对应的变量副本并返回。ThreadLocal与Thread、ThreadLocalMap之间的关系是,每个线程都有一个ThreadLocalMap对象,其中存储了与该线程关联的所有ThreadLocal对象及其对应的变量副本。 ThreadLocal的常见使用场景包括但不限于: - 解决线程安全问题:可以将需要在多个线程中共享的数据存储在ThreadLocal变量中,每个线程访问自己的变量副本,避免了线程安全问题。 - 传递上下文信息:可以将一些需要在多个方法中共享的上下文信息存储在ThreadLocal变量中,在方法调用链中方便地获取这些上下文信息。 - 数据库连接管理:可以将数据库连接存储在ThreadLocal变量中,在每个线程中独立管理数据库连接,避免了线程间的冲突。 总之,ThreadLocal提供了一种方便的方式来实现线程间的数据隔离和传递,能有效地解决多线程并发访问的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [史上最全ThreadLocal 详解](https://blog.csdn.net/qq_43842093/article/details/126715922)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [ThreadLocal详解](https://blog.csdn.net/m0_49508485/article/details/123234587)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值