侧边栏壁纸
博主头像
XiaoLin's Blog 博主等级

XiaoLin的个人博客~

  • 累计撰写 30 篇文章
  • 累计创建 33 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Java 的 ThreadLocal

XiaoLin
2024-02-23 / 0 评论 / 0 点赞 / 29 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1 ThreadLocal 介绍

1.1 基本介绍

ThreadLocal 类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问 ) 时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程上下文。

我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

  1. 线程并发:在多线程的场景下
  2. 传递数据:我们可以通过 ThreadLocal 在同一线程,不同组件中传递公共变量
  3. 线程隔离:每个线程的变量都是独立的,不会互相影响

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 的 keyThreadLocal 本身,value 擦汗 is 真正要存储的值 Object

具体程是这样的:

  1. 每个 Thread 线程内部都有一个 Map(ThreadLocalMap)
  2. Map 里面存储 ThreadLocal 对象(key)和线程的变量副本(value)
  3. Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值
  4. 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本隔离,互不干扰

2.3 JDK8 后设计的好处

  1. 每个 Map 存储的 Entry 数量变少
  2. 当 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);
 }

代码执行流程

  1. 首先获取当前线程,并根据当前线程获取一个 ThreadLocalMap
  2. 如果获取的 Map 不为空,则将参数设置到 Map 钟(当前的 ThreadLocal 为 key)
  3. 如果 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;
    }

代码执行流程

  1. 首先获取当前线程,根据当前线程获取一个 Map
  2. 如果获取的 Map 不为空,则在 Map 中以 ThreadLocal 的引用作为 key 来在 Map 中获取对应的 Entry,否则到 4
  3. 如果 e 不为 null,则返回 e.value 否则转到 4
  4. Map 为空或者 e 为空,则通过 initialValue 函数获取初始值 Value,然后用 ThreadLocal 的引用和 value 作为 firstKey 和 firstValue 创建一个新 Map
0

评论区