蜗牛大师

吴庆龙的学习笔记

导航

ThreadLocal源码分析

1. 概述

很多同学对ThreadLocal并不陌生, 但是可能大多数同学可能是知其然不知其所以然, 所以今天就来分析一下ThreadLocal中的奥妙.

个人知识面不是很广, 很多知识综合不起来, 本文只是针对ThreadLocal的源码进行解析.

2. 实战

先来看一个示例吧.

public class ThreadLocalDemo {

    public static void main(String[] args) {

        // 定义ThreadLocal
        ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
            @Override
            protected String initialValue() {
                return "init value.";
            }
        };

        // 启动线程A
        new Thread(() -> {
            // set的过程实际上是对当前Thread类实例对象中的属性值threadLocals进行赋值
            // 值为 new ThreadLocalMap(this, firstValue);
            // 其中的this是当前的threadLocal实例对象
            threadLocal.set("我是线程A.");

            // get的过程实际上是先获取当前的线程, 然后获取当前线程中的threadLocals属性值(ThreadLocalMap)
            // 然后通过key来获取值, key就是当前的实例对象, 也就是threadLocal实例对象
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());

            // 移除
            threadLocal.remove();
        }, "线程A").start();

        // 启动线程B
        new Thread(() -> {
            threadLocal.set("我是线程B.");
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
            threadLocal.remove();
        }, "线程B").start();

        // 打印主线程中的值
        System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
        threadLocal.remove();

    }

}

看起来很奇怪, 只有一个threadLocal实例对象, 在不同的线程中设置了不同的值, 但是获取的时候却没有混乱. 看起来是不同线程之间的变量各自独立, 它是如何实现的呢? 继续往下看.

3. Thread与ThreadLocal的关系

打开Thread的源码发现, Thread类中有一个属性值是threadLocals, 是这样定义的:ThreadLocal.ThreadLocalMap threadLocals = null;, 可以发现实际上引用的是ThreadLocal类中的内部类ThreadLocalMap.

那么ThreadLocalMap又是什么呢? 通过名字猜测是一个Map, 内部有一个Entry数组存储Entry实例, 而Entry继承了WeakReference(弱引用), 弱引用的特点就是在进行GC的时候, 弱引用会被回收掉. ThreadLocalMap中提供了Map的基本操作, 如set/get/remove.

这里可以猜测一下ThreadLocalMap中存储的key和value都是什么? 下面会有解答.

4. ThreadLocal#set

set无非就是复制操作, 方法名简单, 但是具体实现并不是简单的赋值操作.

public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程.
    ThreadLocalMap map = getMap(t); // 获取线程实例的threadLocals属性值.
    if (map != null)
        map.set(this, value); // this指的是threadLocal对象. value就是传入的值.
    else
        createMap(t, value); // 创建一个Map, 并设置value值.
}
// ----------被调用的方法--------------
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

可以这样理解: Thread类持有ThreadLocalMap类实例, 而ThreadLocalMap类以Map(数组)的形式持有n个Entry(Entry的key为ThreadLocal实例, value为存储数据).

5. ThreadLocal#get

同样, get方法也并不是简单的获取.

public T get() {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取threadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); // 通过当前的threadLocal实例获取对应的值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }

    return setInitialValue();
}
// ----------被调用的方法--------------
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);
    return value;
}

这里比较简单, 就是先获取当前线程, 然后获取线程持有的threadLocalMap对象, 然后通过key(当前的threadLocal)获取对应value值.

但是这里这个setInitialValue方法是什么呢? 里面调用了initialValue方法, 这里返回了一个null值, 其实我们可以重写这个方法, 如下

// 定义ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value.";
    }
};

这样, 方法的返回值就是我们自定义的初始值了.

6. 关于内存泄漏

对于Java同学来说, 内存泄漏这个词比较陌生, 但是对于C的同学来说比较熟悉, 就是分配了内存之后没有进行回收, 因为C是手动分配内存手动进行回收, 而Java是自动的. 如果使用不当则会导致内存泄漏.

关于ThreadLocal的内存泄漏原因, 写文章的时候我还没有理解到位, 大家可以自行百度, 但是这里给出解决方法来避免可能的内存泄漏.

就是 每次使用完成之后要使用threalocal.remove();从Thread的ThreadLocalMap中移除当前的ThreadLocal.

7. 总结

关于ThreadLocal要明白以下几点

  • 了解他的运行原理
  • 避免内存泄漏

posted on 2018-12-25 14:05  蜗牛大师  阅读(290)  评论(0编辑  收藏  举报