一、ThreadLocal使用案例

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

package cn.tx.threadlocal;

public class ThreadLocalTest {
    // (1)print函数
    static void print(String str) {
        //1.1打印当前线程本地内存中localVariable变量的值
        System.out.println(str + ":" + localVariable.get());
        //1.2清除当前线程本地内存中的localVariable变量
        //localVariable. remove () ;
    }



    // (2)创建ThreadLocal变量, 用于存取值的
    static ThreadLocal<String> localVariable = new ThreadLocal<>();

    public static void main(String[] args) {
        //(3) 创建线程one
        Thread threadOne = new Thread(new Runnable() {
            public void run() {
                //3.1设置线程One中本地变量localVariable的值
                localVariable.set("threadOne local variable");
                //3.2调用打印函数
                print("threadOne");
                //3.3打印本地变量值
                System.out.println("threadOne remove after" + ":" + localVariable.get());
            }
        });

        // (4) 创建线程two
        Thread threadTwo = new Thread(new Runnable() {
            public void run() {
                //4.1 设置线程Two中本地变量localVariable的值
                localVariable.set("threadTwo local variable");
                //4.2调用打印函数
                print("threadTwo");
                //4.3打印本地变量值
                System.out.println("threadTwo remove after" + ":" + localVariable.get());
            }
        });
        //(5)启动线程
        threadOne.start();
        threadTwo.start();
    }
}



当1.2的代码注释掉之后,输出结果如下:
在这里插入图片描述
虽然两个线程都调用set方法,在localVariable里面设置了值,但是最后输出的时候还是自己输出自己的值,没有共享。

当1.2的代码打开之后,输出结果如下:
在这里插入图片描述
第一个线程remove之后再get,输出的是null,说明第二个线程设置的值访问不到,不是共享的。

二、ThreadLocal的原理

1.首先明确几个概念

(1)Thread:线程实例的「数据载体」

每个线程实例内部持有两个 ThreadLocalMap,真正存储线程私有数据的地方就是这两个 Map:

  1. threadLocals:当前线程的私有 ThreadLocalMap → 存储该线程的 ThreadLocal 数据,默认 null,第一次调用 ThreadLocal.set()/get() 时才会创建。
  2. inheritableThreadLocals:可继承的 ThreadLocalMap → 用于子线程继承父线程的 ThreadLocal 数据(比如父线程设置的值,子线程能直接拿到)。
(2)ThreadLocalMap

ThreadLocalMap是一个定制化的Hashmap,他的键是ThreadLocal 实例,值是你set后面的值。
由于HashMap的键唯一,单个 ThreadLocal 实例(Key)在单个线程的 ThreadLocalMap 中,只能对应 1 个 Value。

(3)ThreadLocal:线程私有数据的「操作工具」

它是一个泛型类,本身不存储数据,只负责操作线程的 ThreadLocalMap

  • +set(value: T): void:公开方法 → 给当前线程设置 ThreadLocal 私有数据。
  • +get(): T:公开方法 → 获取当前线程的 ThreadLocal 私有数据。
  • +remove(): void:公开方法 → 移除当前线程的 ThreadLocal 数据(避免内存泄漏)
  • #getMap(t: Thread): ThreadLocalMap:包访问方法 → 拿到指定线程的 ThreadLocalMap。
  • #createMap(t: Thread, firstValue: T): void:包访问方法 → 为线程创建 ThreadLocalMap,并存入初始值。
    -setInitialValue(): T:私有方法 → 当调用 get() 但未设置值时,生成初始值(默认返回 null,可重写)。

2.下面具体分析ThreadLocal的原理

2.1set方法
public void setValue(T vaue){
    //(1)获取当前线程
    Thread t = Thread. currentThread() ;
    // (2)将当前线程作为key,去查找对应的线程变量,找到则设置
    ThreadLocalMap map = getMap(t) ;
    if (map != null)
        map.set (this, value) ;
    else
    //(3)第一次调用就创建当前线程对应的HashMap
        createMap(t, value) ;
}

以调用 ThreadLocal.set(“张三的手机号”) 为例:

  • ThreadLocal.set() 先通过 Thread.currentThread() 拿到当前线程(Thread 实例)。
  • 调用getMap(t) 获取该线程的 threadLocals(ThreadLocalMap)。
  • 如果 ThreadLocalMap 已存在→ 以当前 ThreadLocal 为 Key,把值存入 Map;
  • 如果 ThreadLocalMap 不存在 → 调用createMap(t, value) 创建新的 ThreadLocalMap,并赋值给线程的 threadLocals。
2.2get方法
public T get() {
    //(4) 获取当前线程
    Thread t = Thread. currentThread() ;
    // (5)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t) ;
    // (6)如果threadLocals不为null,则返回对应本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map. getEntry(this) ;
        if (e != null) {
            @SuppressWarnings ("unchecked" )
            T result = (T)e.value;
            return result;}}
    // (7) threadLocals为空则初始化当前线程的threadLocals成员变量
    return setInitialValue() ;
}

2.3remove方法

会删除当前线程的threadLocals中的本地变量。

public void remove () {
    ThreadLocalMap m = getMap (Thread. currentThread());
    if (m != null)
        m. remove (this) ;
}

3.ThreadLocal不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。
根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程
为thread 线程,而这里调用set 方法设置线程变量的是main线程,两者是不同的线程,自
然子线程访问时返回null。那么有没有办法让子线程能访问到父线程中的值?答案是有。InheritableThreadLocal 应运而生。

三、InheritableThreadlocal类

public class ThreadLocalTest1 {

    //(1)创建线程变量
    public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();

    public static void main(String[] args) {
        //(2) 设置线程变量
        threadLocal.set("hello world");
        //(3) 启动子线程
        Thread thread = new Thread(new Runnable() {
            public void run() {
                //(4) 子线程输出线程变量的值
                System.out.println("thread:" + threadLocal.get());
            }
        });
        thread.start();
        //(5). 主线程输出线程变量的值
        System.out.println("main:" + threadLocal.get());
    }
}

使用InheritableThreadLocal创建实例的话,子线程能输出主线程的值,但是如果使用ThreadLocal创建实例的话,子线程不能输出主线程的值。

那这是为什么呢?
InheritableThreadLocal是继承自ThreadLocal的,重写了下面三个方法

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    // 1. 重写:父线程值传递给子线程时的转换逻辑(默认直接返回父线程值,可重写定制)
    protected T childValue(T parentValue) {
        return parentValue;
    }

    // 2. 重写:获取线程的 inheritableThreadLocals 而非 threadLocals
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    // 3. 重写:创建 Map 时赋值给 inheritableThreadLocals 而非 threadLocals
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

下面是Thread构造器当中的代码片段
在这里插入图片描述

  1. 父线程设置值:父线程通过 InheritableThreadLocal.set() 把值存入自己的inheritableThreadLocals(而非 threadLocals)。
  2. 子线程创建触发复制:父线程新建子线程时,Thread构造器会调用 init 方法,检查父线程的 inheritableThreadLocals 是否存在。
  3. 复制 Map到子线程:如果存在,调用 ThreadLocal.createInheritedMap() 复制父线程的 inheritableThreadLocals,生成子线程自己的inheritableThreadLocals(浅拷贝,值为引用类型时父子线程共享对象)。
  4. 子线程获取继承的值:子线程通过InheritableThreadLocal.get() 从自己的 inheritableThreadLocals中获取父线程传递过来的值。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐