ThreadLocal 子类及实现原理

本文详细介绍了Java中的ThreadLocal类,包括它的作用、原理和使用场景。ThreadLocal为每个线程提供了独立的变量副本,解决了多线程并发访问共享变量的安全问题。文中还分析了ThreadLocal的set、get和remove方法的实现,以及内存泄漏可能带来的问题。示例代码展示了ThreadLocal在不同线程间的不共享特性。

        我们在介绍threadLocal子类及实现原理之前,先介绍一下threadLocal类。

ThreadLocal类简介

        多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种规避多线程访问出现线程不安全的方法当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题

 ThreadLocal原理

        ThreadLocal 是为了解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal 并不是一个Thread,而是 Thread 的局部变量。

        ThreadLocal 用于保存某个线程共享变量:对于同一个 static ThreadLocal,其为每个使用该变量的线程提供独立的变量副本,不同线程只能从中 get、set 和 remove 自己的变量,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就像是线程的本地变量。

 ThreadLocal使用场景(ThreadLocal的应用场景 - sw_kong - 博客园   

         这种场景通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat

        SimpleDateFormat对象是非线程安全的,如果多个线程需要使用,就必须每个线程创建一个SimpleDateFormat对象来确保线程安全,这样会增大内存开销。

ThreadLocal用法   

        void set(Object value)设置当前线程的线程局部变量的值

        public Object get()返回当前线程所对应的线程局部变量

        public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

        protected Object initialValue()返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一个 null。

ThreadLocal源码分析(ThreadLocal - 简书    

主要方法:

set()方法:

public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}



ThreadLocalMap getMap(Thread t) {
    //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
    return t.threadLocals; 
}


void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

        从threadlocal的set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。

        从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。

ThreadLocalMap

        通过上面的代码不难看出在实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置

        每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法可以理解成每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。

        对于一个Thread来说,只会持有一个ThreadLocalMap,所以A、B、C对应同一个ThreadLocalMap对象。为了管理A、B、C,于是将他们存储在一个数组的不同位置,而这个数组就是上面提到的Entry型的数组table。

get()方法:

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

ThreadLocalMap中getEntry()方法:

setInitialValue()方法:

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

remove()方法:

        remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

public void remove() {
   //获取当前线程绑定的threadLocals
    ThreadLocalMap m = getMap(Thread.currentThread());
    //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
    if (m != null)
       m.remove(this);
}

ThreadLocal不支持继承性

        同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)。

package com.redistext.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/11/6 10:16
 * @description: V1.0
 */
public class StreamTest {

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

    public static void main(String[] args) {

        //在main线程中添加main线程的本地变量
         threadLocal.set("mainVal");
         //新创建一个子线程
         Thread thread = new Thread(new Runnable() {
             @Override
             public void run() {
                 System.out.println("子线程中的本地变量值:"+threadLocal.get());
             }
         });
        thread.start();
        //输出main线程中的本地变量值
        System.out.println("main线程中的本地变量值:"+threadLocal.get());
    }


}

        结果:

mainx线程中的本地变量值:mainVal
子线程中的本地变量值:null

        ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能

ThreadLocal内存泄漏问题(没看太明白):

Java中的ThreadLocal详解 - 夏末秋涼 - 博客园

ThreadLocal的内存泄露?什么原因?如何避免? - 知乎

参考文档:

        Java 多线程:InheritableThreadLocal 实现原理_ni357103403的博客-CSDN博客

        ThreadLocal的原理及用法 - 简书

### ThreadLocal 的底层实现机制 ThreadLocalJava 中提供的一种能够让线程内部存储变量副本的工具,使得不同线程能够独立访问自己的变量副本而不受其他线程的影响。这种特性对于多线程环境下的资源管理非常重要。 #### 底层数据结构 Thread 维护了一个名为 `threadLocals` 的成员变量,它是一个自定义类型的数组 `ThreadLocal.ThreadLocalMap[]`[^1]。每当调用 `set()` 或 `get()` 方法时,实际上是在操作这个 Map 对象。具体来说: - **ThreadLocalMap**: 这个类是 ThreadLocal 实现的核心部分之一,它是保存键值对的数据结构,其中 key 就是指向当前使用的 ThreadLocal 变量对象本身,而 value 则是我们通过 set() 存入的具体值。 ```java static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private Entry[] table; ... } ``` - **弱引用 (Weak Reference)**: Key 使用的是弱引用来指向 ThreadLocal 实例,这意味着如果某个 ThreadLocal 没有被任何强引用持有,则可以在垃圾回收期间被清除掉,从而防止内存泄漏问题的发生。 #### 主要方法解析 - **初始化 (`initialValue`)** 当第一次调用 get() 方法获取尚未赋过初值的 ThreadLocal 值时会触发此方法,默认返回 null;子类可以根据需求重写该方法来指定默认初始值。 - **存取 (`set`, `get`)** 调用 set(T value) 和 get() 方法实际上是针对当前线程所持有的 ThreadLocalMap 执行 put 和 get 操作。每次都会先尝试定位到对应的 entry 如果不存在则新建一个 entry 放置进去。 - **移除 (`remove`)** remove() 方法用于清理不再需要的 ThreadLocal 数据项,这有助于减少潜在的内存泄露风险。 #### 图解说明 为了更好地理解上述过程,下面给出了一张简化版的工作流程图: ![ThreadLocal 工作原理](https://img-blog.csdnimg.cn/20210718194536.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0LnRoYW5rYXNoYW4=,size_16,color_FFFFFF,t_70) 在这个过程中可以看到,当多个线程同时访问同一个 ThreadLocal 类型的对象时,它们各自拥有自己的一份独立拷贝,并不会相互干扰。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值