ThreadLocal源码级别详解

ThreadLocal的作用

ThreadLocal作用是可以实现每个线程都有自己专属的本地变量。在实际的Web开发中,可以用该类来保存用户信息等,这样对于对于一个应用来说每个工作线程可以通过不同的用户信息处理业务逻辑,ThreadLocal另外一个用途就是可以用来减少方法的入参。

ThreadLocal的使用

下面是一个使用ThreadLocal的简单Demo:

java 复制代码
public class DemoThreadLock {

    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "hello, word!");
    
    public static void main(String[] args) {
        new Thread(new Worker(), "Thread1").start();
        new Thread(new Worker(), "Thread2").start();
    }
    
    static class Worker implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-初始值为:" + threadLocal.get());
            threadLocal.set(Thread.currentThread().getName());
            try {
                //等地啊1s保证每个线程都设置完自己的值
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "-最终值为:" + threadLocal.get());
        }

    }
}

上面代码最终的执行结果是:

arduino 复制代码
Thread2-初始值为:hello, word!
Thread1-初始值为:hello, word!
Thread1-最终值为:Thread1
Thread2-最终值为:Thread2

在实现了Runnale接口的Worker类中,每个线程给threadLocal赋完值以后等待1s,这样能保证两个线程都是在赋完值以后再执行打印。从最终的结果可以看到两个线程打印结果不同。

ThreadLocal源码

创建对象

  • 无参构造方法创建

  • 使用ThreadLocal静态方法:withInitial(Supplier<? extends S> supplier),参方法将会创建一个SuppliedThreadLocal对象,这个对象是ThreadLocal的静态内部类,该类继承了类ThreadLocal,并重写了ThreadLocal的空方法initialValue()

    java 复制代码
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    
        private final Supplier<? extends T> supplier;
    
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
    
        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

方法get()

在Thread实例中ThreadLocalMap类型属性threadLocals并不是在创建Thread时创建的,而是当我们调用ThreadLocal的get()或set()方法时才会创建。

java 复制代码
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

在第三行getMap(t)方法中,获取当前线程的ThreadLocalMap类型属性。由于在第一次调用该方法的时候,该属性还没有被创建所以为null,将会返回setInitialVaule()方法创建的对象。如果map不为null时,将会通过当前ThreadLocal对象做为key,在ThreadLocalMap中获取对应的Entry(该类继承了WeakReference构造该对象时会将key设置为弱引用,而vaule仍然未强引用,这也就是导致内存泄漏的原因),然后返回Entry包含的值。

接下来我们来看看方法setInitialValue(),在该方法中将会创建Thread的属性threadLocals

java 复制代码
private T setInitialValue() {
    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;
}

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

首先,通过initialValue()方法获取初始值(在前面介绍ThreadLocal构造时,我们知道在方法静态方法withInitial中会返回一个重写了该方法的类SuppliedThreadLocal),接着获取到当前线程中的ThreadLocalMap属性得到map,此时map仍为null,所以将会调用createMap方法给当前线程实例赋值一个新创建的ThreadLocalMap实例。

方法set()

java 复制代码
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

方法set()很简单,获取当前线程实例的属性ThreadLocalMap,如果该对象为null,那么就创建一个对象并赋值给Thread实例的属性,并带有初始值value.

ThreadLocal内存泄漏

首先我们来了解两个相关的概念

  1. 内存泄漏(Memory Leak):是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。
  2. 弱引用:如果一个对象的引用是弱引用,那么在JVM进行垃圾回收时该对象将会被回收(除了弱引用还有强引用,软引用,虚引用)。

ThreadLocal中保存的值最终将会被包装成Entry(ThreadLocal中的静态类ThreadLocalMap中的静态类Entry),该类继承了WeakReference,构造该对象时会将key设置为弱引用,但是vault还是强引用。这样在JVM垃圾回收的时候,key就会被回收而value不会,这样就会存在key为null的Entry,如果不处理这些问题value将永远不会被回收。

java 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
相关推荐
我命由我123452 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
武子康4 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
YuTaoShao7 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw7 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨7 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
双力臂4048 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
Edingbrugh.南空8 小时前
Aerospike与Redis深度对比:从架构到性能的全方位解析
java·开发语言·spring
QQ_4376643149 小时前
C++11 右值引用 Lambda 表达式
java·开发语言·c++
永卿0019 小时前
设计模式-迭代器模式
java·设计模式·迭代器模式
誰能久伴不乏9 小时前
Linux如何执行系统调用及高效执行系统调用:深入浅出的解析
java·服务器·前端