每日一博 - 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 方法

相关文章
|
20天前
|
存储 缓存 安全
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
48 0
|
20天前
|
存储 Java 编译器
ThreadLocal、InheritThreadLocal、TransmittableThreadLocal
ThreadLocal、InheritThreadLocal、TransmittableThreadLocal
|
20天前
|
存储 Java 数据管理
ThreadLocal的使用
`ThreadLocal`是Java中的线程局部变量工具,确保每个线程都有自己的变量副本,互不干扰。适用于保持线程安全性数据和跨方法共享数据。基本用法包括创建实例、设置和获取值以及清除值。例如,创建ThreadLocal对象后,使用`.set()`设置值,`.get()`获取值,`.remove()`清除值。注意ThreadLocal可能引起内存泄漏,应适时清理,并谨慎使用以避免影响代码可读性和线程安全性。它是多线程编程中实现线程局部数据管理的有效手段。
47 10
|
20天前
|
存储 前端开发 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理1
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
281 0
|
20天前
|
设计模式 缓存 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理3
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
178 1
|
20天前
|
存储 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理2
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
123 0
|
9月前
|
Java
ThreadLocal详解
ThreadLocal详解
42 0
|
11月前
|
缓存 安全 Java
浅谈ThreadLocal
浅谈ThreadLocal
113 0
|
缓存 Java 测试技术
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(下)
每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal(下)
95 0
|
12月前
|
存储 SQL Java
ThreadLocal的其他应用
request对象跟PageHelper
85 0