每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(上)

简介: 每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal

d0fdb2e70e1847b2b9749789048967d3.png

ThreadLocal

位于java.lang包中的ThreadLocal 。



743e7699f10d42f0bb1619c82ecba07f.png


多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。


ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。


ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题


核心API


9d67ddc490c34adab9cb86ff6a8f1b1a.png


  • public T get() 从线程上下文环境中获取设置的值
  • public void set(T value) 将值存储到线程上下文环境中,供后续使用
  • public void remove() 清除线程本地上下文环境


ThreadLocal类


59cbdee102d94f5c8471ded318af021e.png


8154a0ad6ecc49a6ba4fd111872fe9e5.png


【数据存储位置】


当线程调用 threadLocal 对象的 set(Object value) 方法时,数据并不是存储在 ThreadLocal 对象中,而是存储在 Thread 对象中,这也是 ThreadLocal 的由来,具体存储在线程对象的threadLocals 属性中,其类型为 ThreadLocal.ThreadLocalMap


【ThreadLocal.ThreadLocalMap】

Map 结构,即键值对,键为 threadLocal 对象,值为需要存储到线程上下文的值(threadLocal#set)方法的参数


源码分析

set

   public void set(T value) {
      // 获取当前线程 
        Thread t = Thread.currentThread();
         // 获取线程的 threadLocals 属性
        ThreadLocalMap map = getMap(t);
        // 如果不为空,设置k  v 
        if (map != null)
            map.set(this, value);
        else
          // 初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。
            createMap(t, value);
    }


get

public T get() {
    // 获取当前线程 
        Thread t = Thread.currentThread();
        // 获取线程的 threadLocals 属性
        ThreadLocalMap map = getMap(t);
    // 如果线程对象的 threadLocals 属性不为空,则从该 Map 结构中,用 threadLocal 对象为键去查找值,如果能找到,则返回其 value 值,否则执行代码 setInitialValue()
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 果线程对象的 threadLocals 属性为空,或未从 threadLocals 中找到对应的键值对,则调用该方法执行初始化
        return setInitialValue();
    }


 private T setInitialValue() {
    // 调用 initialValue() 获取默认初始化值,该方法默认返回 null,子类可以重写,实现线程本地变量的初始化。
        T value = initialValue();
        // 获取当前线程。
        Thread t = Thread.currentThread();
        // 获取该线程对象的 threadLocals 属性。
        ThreadLocalMap map = getMap(t);
        // 如果不为空,则将 threadLocal:value 存入线程对象的 threadLocals 属性中。
        if (map != null)
            map.set(this, value);
        else
          // 否则初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。
            createMap(t, value);
        return value;
    }
/**
*
* 初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。
*/
  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

remove

     public void remove() {
      // 获取该线程对象的 threadLocals 属性。
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
         // 移除
             m.remove(this);
     }


缺陷

ThreadLocal 无法在父子线程之间传递, 看源码我们也知道了,都是Thread.currentThread.

那我们来证明下吧


public class ThreadLocalTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        threadLocal.set("artisan Test");
        doSomething();
    }
    private static void doSomething() {
        System.out.println("threadLocal中的对象:" + threadLocal.get());
        new Thread(()->{
            System.out.println("开启子线程");
            System.out.println("子线程中获取threadLocal:" + threadLocal.get());
        }).start();
    }
}

05b7e05c36864844b9b9d936d0649c88.png


d8672c659d47422ab41f9b1dd7c73947.png

InheritableThreadLocal


由于 ThreadLocal 在父子线程交互中子线程无法访问到存储在父线程中的值,无法满足某些场景的需求,比如链路跟踪


f96692b7dac54a7daaa809e262ec2e86.png


为了解决上述问题,JDK 引入了 InheritableThreadLocal,即子线程可以访问父线程中的线程本地变量,准确的说是子线程可以访问在创建子线程时父线程当时的本地线程变量,其实现原理是在创建子线程将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。



源码解析


public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  .....
}

f649a9b5d8f04e219f60bc5508d9652d.png


从类的继承层次来看,InheritableThreadLocal 只是在 ThreadLocal 的 get、set、remove 流程中,重写了 getMap、createMap 方法,整体流程与 ThreadLocal 保持一致,所以我们初步来看一下InheritableThreadLocal 是如何重写上述这两个方法的。

  /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

和 ThreadLocal比一比


5794f46c359b45f5af0e77af3c7d94b4.png


可以知道 ThreadLocal 操作的是 Thread 对象的 threadLocals 属性,而 InheritableThreadLocal 操作的是 Thread 对象的 inheritableThreadLocals 属性


   /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }


createMap 被执行的条件是调用 InheritableThreadLocal#get、set 时如果线程的inheritableThreadLocals 属性为空时才会被调用

咦 ,看到这里没啥用啊


5b9ef014f66d46dea0d4cff999bc20ad.png

InheritableThreadLocal 是如何继承自父对象的线程本地变量的呢?

那就得看 Thread#init 方法

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
    //  获取当前线程对象,即待创建的线程的父线程。
        Thread parent = currentThread();
         ........
         ........
         ........
        // 如果父线程的 inheritableThreadLocals 不为空并且 inheritThreadLocals 为 true(该值默认为true),则使用父线程的 inherit 本地变量的值来创建子线程的 inheritableThreadLocals 结构,即将父线程中的本地变量复制到子线程中
        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();
    }
 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
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) {
                        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++;
                    }
                }
            }
        }


类似于 Map 的复制,只不过其在 Hash 冲突时,不是使用链表结构,而是直接在数组中找下一个为 null 的槽位


子线程默认拷贝父线程的方式是浅拷贝,如果需要使用深拷贝,需要使用自定义ThreadLocal,继承 InheritableThreadLocal 并重写 childValue 方法

相关文章
|
域名解析 缓存 网络协议
提升你的外国服务器网站国内访问速度~
由于众所周知的原因,国内访问国外的服务器速度较慢。在没有特殊线路(直连、CN2GIA等)的加持下,路由线路左绕右绕,严重影响国内访问速度。 能使用国内服务器当然是最好的,但是高昂的流量&带宽价格以及域名备案门槛让人劝退。所以,本文章提供的加速方案是针对线路一般的海外服务器网站访问速度慢的问题。
8046 0
提升你的外国服务器网站国内访问速度~
|
9月前
|
中间件 Linux vr&ar
Centos7升级Glibc
centos7升级glic问题
2084 0
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十八)(2)
SqlAlchemy 2.0 中文文档(四十八)
302 0
|
开发框架 移动开发 JavaScript
SpringCloud微服务实战——搭建企业级开发框架(四十六):【移动开发】整合uni-app搭建移动端快速开发框架-环境搭建
正如优秀的软件设计一样,uni-app把一些移动端常用的功能做成了独立的服务或者插件,我们在使用的时候只需要选择使用即可。但是在使用这些服务或者插件时一定要区分其提供的各种服务和插件的使用场景,例如其提供的【uni-starter快速开发项目模版】几乎集成了移动端所需的所有基础功能,使用非常方便,但是其许可协议只允许对接其uniCloud的JS开发服务端,不允许对接自己的php、java等其他后台系统。
625 61
|
前端开发 JavaScript Java
毕业设计|基于SpringBoot+Vue的新生报道
毕业设计|基于SpringBoot+Vue的新生报道
332 1
|
Windows
苹果笔记本如何安装windows系统
苹果笔记本如何安装windows系统
1598 1
|
数据采集 搜索推荐 大数据
基于大数据的市场分析与消费者行为研究
【6月更文挑战第5天】大数据在市场分析与消费者行为研究中扮演关键角色。通过海量数据分析,企业能更全面、精准地了解消费者偏好和市场趋势。Python等工具帮助处理数据,揭示购买习惯,支持个性化营销策略。同时,大数据使深入理解消费者心理、决策过程成为可能,助力企业优化产品,提升客户满意度和忠诚度。在这个数据驱动的时代,大数据是洞悉市场和消费者的魔法力量。
661 2
|
关系型数据库 MySQL 测试技术
探索MySQL间隙锁的奥秘
MySQL中的间隙锁(Gap Lock)是一种锁机制,用于在多个事务中保护数据的一致性。它主要用于防止并发事务插入新数据或者修改已有数据时,导致其他事务读取到不一致的结果。
探索MySQL间隙锁的奥秘
|
存储 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理2
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
1459 0
|
SQL otter Oracle
otter
otter
957 2