ThreadLocal基础知识详解(一)

344 阅读3分钟

在正式开始之前,从一小段代入手:

public static void main(String[] args) {
    ThreadLocal threadLocal = new ThreadLocal();
    threadLocal.set(5);
    System.out.println(threadLocal.get());
}

源码:ThreadLocal类set()方法

看到源码不要慌,不懂往下翻

tempImage1638770048679.gif

public class ThreadLocal<T> {

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
}

ThreadLocal类get()方法

public class ThreadLocal<T> {
    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

}

大家可以尝试断点执行下,大致明白整个执行过程。

我们从这段简单的代码中逐步分析

1. ThreadLocal初始化

ThreadLocal threadLocal = new ThreadLocal();

查看源码:

public ThreadLocal() {
}

可以看到构造函数并没有做任何处理。

2. ThreadLocal设置值

threadLocal.set(5);

image.png

image.png

image.png

获取类型为ThreadLocalMap的map

image.png 以当前的threadLocal作为key,我们要存的对象5作为value存放到map中。

3. ThreadLocal获取值

threadLocal.get()

image.png

当前threadLocal作为key,获取map中Entry对象

image.png

获取对象的value值

image.png

看完上面大致的执行过程,初次接触的宝子们一定还一头雾水

tempImage1638769706723.gif

ThreadLocalMap是啥?Entry是啥?

接下来让我们走近源码,一层一层拨开ThreadLocal神秘的面纱

image.png

源码剖析

Tip1

首先大家看下ThreadLocal的整个结构

public ThreadLocal() {
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {   
        }
    }
}

一共有3层结构
ThreadLocalMapThreadLocal静态内部类
EntryThreadLocalMap静态内部类

真正存储数据的是ThreadLocalMap类型数据,ThreadLocal只是作为key值。

首先看下ThreadLocalMap是怎么实现存储的

static class ThreadLocalMap {
        // Entry继承 WeakReference弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            // 实际存储的对象value
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        // 初始化容量
        private static final int INITIAL_CAPACITY = 16;
        
        // Entry类型的数组
        private Entry[] table;
        
        // 大小
        private int size = 0;
    }

不理解没关系,我们来用实际代码演练下:

image.png

public static void main(String[] args) {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("test string");
    logger.info(threadLocal.get());
}

可以看到我创建的threadLocal hashCode为'-2027808484' image.png 继续执行 image.png 此时main线程中threadLocals对象已经不再是null了,这是为什么呢?
因为main线程启动后做了其他的一些初始化操作,后续可以探究。

image.png 执行完set操作后,可以看到table的size已经变成了3
hashCode为'-2027808484'的threadLocal作为key也被维护到了threadLocals的table中,value就是我们要存储的字符串对象“test string”。

image.png image.png image.png 在通过get获取值的时候,通过当前threadLocal的hashCode作为key值从threadLocals取出对象,再通过getValue()拿到我们存储的字符串对象。

每个线程都有自己的threadLocals对象
在Thread类中,只创建threadLocals对象,真正的维护是在ThreadLocal类中

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}