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;
    }
}
相关推荐
煸橙干儿~~几秒前
分析JS Crash(进程崩溃)
java·前端·javascript
2401_854391081 分钟前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
Amor风信子2 分钟前
华为OD机试真题---跳房子II
java·数据结构·算法
杨荧28 分钟前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
陈逸轩*^_^*1 小时前
Java 网络编程基础
java·网络·计算机网络
这孩子叫逆1 小时前
Spring Boot项目的创建与使用
java·spring boot·后端
星星法术嗲人1 小时前
【Java】—— 集合框架:Collections工具类的使用
java·开发语言
一丝晨光1 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
天上掉下来个程小白1 小时前
Stream流的中间方法
java·开发语言·windows
xujinwei_gingko2 小时前
JAVA基础面试题汇总(持续更新)
java·开发语言