1 ThreadLocal 介绍
1.1 基本介绍
ThreadLocal 类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问 ) 时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
- 线程并发:在多线程的场景下
 - 传递数据:我们可以通过 ThreadLocal 在同一线程,不同组件中传递公共变量
 - 线程隔离:每个线程的变量都是独立的,不会互相影响
 
1.2 基本使用
1.2.1 常用方法
方法声明
描述
ThreadLocal()
构造方法,用于创建对象
public void set(T value)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
删除当前线程绑定的局部变量(注意:该操作特别重要,防止内存溢出)
1.2.2 使用案例
package xyz.xiaolinz.demo1;
/**
* 
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
 *
 * ThreadLocal:
 *  1. set(): 给当前变量绑定一个值
 *  2. get(): 获取当前变量的值
**/
public class ThreadLocalDemo {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        final ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                demo.set(Thread.currentThread().getName() + "的数据");
                System.out.println(Thread.currentThread().getName() + "====>" + demo.get());
                // 使用完成时显示调用remove()方法
                demo.remove();
            }).start();
        }
    }
    /**
     * 绑定变量到当前线程
     *
     * @param value 值
     */
    public void set(String value) {
        threadLocal.set(value);
    }
    /**
     * 获取当前线程绑定的变量
     *
     * @return 值
     */
    public String get() {
        return threadLocal.get();
    }
    /**
     * 去除绑定变量
     * 该方法不是必须的,因为ThreadLocalMap中的Entry是使用弱引用的,所以当线程结束时,会自动被回收,不会造成内存泄漏
     * 但是当其他对象持有ThreadLocal的引用时,就需要手动调用该方法去除绑定变量,否则会造成内存泄漏
     */
    public void remove() {
        threadLocal.remove();
    }
}
  1.3 ThreadLocal 类与 synchronized 关键字
1.3.1 synchronized 同步方式
synchronized 简单案例
package xyz.xiaolinz.demo2.demo1;
/**
* 
* @author huangmuhong
* @version 1.0.0
* @date 2023/1/12
*
 *
 * ThreadLocal:
 *  1. set(): 给当前变量绑定一个值
 *  2. get(): 获取当前变量的值
**/
public class SynchronizedDemo {
    private String content;
    public static void main(String[] args) {
        final SynchronizedDemo demo = new SynchronizedDemo();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (demo) {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println(Thread.currentThread().getName() + "====>" + demo.getContent());
                }
            }).start();
        }
    }
    /**
     *
     *
     * @return 值
     */
    public String getContent() {
            return content;
    }
    /**
     *
     *
     * @param value 值
     */
    public void setContent(String value) {
            content = value;
    }
}
  从结果可以发现,加锁的方案同样也可以实现线程隔离的效果,但是在代码中加锁会导致程序失去了并发性,程序只能阻塞的等待前一个线程将 demo 对象的锁释放后才能继续去操作 content 的值。所以给该方案降低了程序的性能
1.3.2 ThreadLocal 与 synchronized 的区别
虽然 ThreadLocal 模式与 synchronized 关键字都用于处理多线程并发访问变量的问题,不过两者处理的角度和思路不同
synchronized
ThreadLocal
原理
同步机制采用了“以时间换空见”的方式,只提供了一份变量,让不同的线程派对访问
ThreadLocal 采用“以空间换时间”的方式,为每个线程都提供了一份变量父本,从而实现同时访问而互不干扰
侧重点
多个线程间访问资源的同步 (阻塞)
多线程中让每个线程之间的数据相互隔离
总结:在刚刚的案例中,ThreadLocal 和 synchronized 都能解决问题,但是使用 ThreadLocal 是最佳选择,我们在开发中通长都会选择以“空间换时间”的方案将性能最大化,但是使用 ThreadLocal 需要注意内存泄漏的问题
2. ThreadLocal 的内部结构
.1 常见的误解
如外恩不去看源代码的话,可能会猜测 ThreadLocal 是这样子设计的:每个 ThreadLocal 都创建一个 Map,然后用线程作为 Map 的 key,要存储的局部比昂量作为 Map 的 value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK 最早期的 ThreadLocal 确实是这样设计的,但现在早已不是了。
2.2 现在的设计
但是,JDK 后面优化了设计方案,在 JDK8 中 ThreadLocal 的设计是:每个 Thread 维护一个 ThreadLocalMap ,这个 Map 的 key 是 ThreadLocal 本身,value 擦汗 is 真正要存储的值 Object。
具体程是这样的:
- 每个 Thread 线程内部都有一个 Map(ThreadLocalMap)
 - Map 里面存储 ThreadLocal 对象(key)和线程的变量副本(value)
 - Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值
 - 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本隔离,互不干扰
 
2.3 JDK8 后设计的好处
- 每个 Map 存储的 Entry 数量变少
 - 当 Thread 销毁时,ThreadLocalMap 也会随之销毁,减少内存的使用
 
3. ThreadLocal 的核心方法源码
基于 ThreadLocal 的内部结构,我们继续分析核心源码,深入了解原理;
方法:
方法声明
描述
protected T initalValue()
返回当前线程局部变量的初始值
public void set(T set)
设置当前线程绑定的局部变量
public T get()
获取当前线程绑定的局部变量
public void remove()
移除当前线程绑定的局部变量
3.1 set 方法
/**
	* 设置当前线程对应的ThreadLocal的值
	* @param value 将要保存在当前线程对应的ThreadLocal的值
	*/
public void set(T value) {
				// 获取当前线程对象
        Thread t = Thread.currentThread();
				// 获取到线程维护的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
				// 往map填充key为ThreadLocal,value为value副本变量的记录
				// 可以看到ThreadLocal本身并不存储任何变量的副本值,其存在更像操作ThreadLocalMap的工具类
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
}
/**
	* 获取当前线程Thread对象维护的ThreadLocalMap
	*/
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
 }
/*
	*创建ThreadLocalMap,用于在当前线程(Thread)维护ThreadLocalMap为空的情况下使用
	*/
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
 }
  代码执行流程
- 首先获取当前线程,并根据当前线程获取一个 ThreadLocalMap
 - 如果获取的 Map 不为空,则将参数设置到 Map 钟(当前的 ThreadLocal 为 key)
 - 如果 Map 为空,则给该线程直接 new 一个 ThreadLocalMap,并将值记录
 
3.2 get 方法
/**
	* 返回当前线程中保存着ThreadLocal的值
	* 如果当前线程没有刺ThreadLocal变量
	* 则它会通过调用{@link [[initialvalue]]}方法进行初始化值
	* 
	*/
public T get() {
				// 获取当前线程
        Thread t = Thread.currentThread();
				// 获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
				// 判断map存不存在
        if (map != null) {
						// 以当前ThreadLocal为key,调用getEntry获取对应的存储实体
            ThreadLocalMap.Entry e = map.getEntry(this);
						// 对结果实体进行判空
            if (e != null) {
                @SuppressWarnings("unchecked")
								// 实际结果
                T result = (T)e.value;
                return result;
            }
        }
				/*
						初始化:有两种情况执行当前代码
						第一种情况:map不存在,表示此线程没有维护的ThreadLocalMap对象
						第二种情况:map存在,但是没有与当前ThreadLocal关联的Entry
				*/
        return setInitialValue();
}
/**
	* 当读取不到对应值时使用当前方法初始化
	*/
private T setInitialValue() {
				// 调用initialValue方法获取初始值
				// 此方法可以被子类重写,如果不重写返回null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
           createMap(t, value);
       }
       if (this instanceof TerminatingThreadLocal) {
           TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
  代码执行流程
- 首先获取当前线程,根据当前线程获取一个 Map
 - 如果获取的 Map 不为空,则在 Map 中以 ThreadLocal 的引用作为 key 来在 Map 中获取对应的 Entry,否则到 4
 - 如果 e 不为 null,则返回 e.value 否则转到 4
 - Map 为空或者 e 为空,则通过 initialValue 函数获取初始值 Value,然后用 ThreadLocal 的引用和 value 作为 firstKey 和 firstValue 创建一个新 Map