ThreadLocal和InheritablThreadLocal核心源码详解

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal和InheritablThreadLocal核心源码详解相关的知识,希望对你有一定的参考价值。

目录

一、ThreadLocal介绍

二、ThreadLocal核心方法解析

1) initialValue()

2) setIntialValue()

3) set()

4) get()

ThreadLocal是怎么做到多线程环境下副本隔离的?

三、ThreadLocalMap 源码解析

1) ThreadLocalMap是在什么时候初始化的呢?

2) Thread和value在ThreadLocalMap里是怎么存放的?

3) InheritablThreadLocal又是怎么存放的?

四、InheritableThreadLocal核心原理

1) inheritableThreadLocal究竟是何物?

2) 案例演示


一、ThreadLocal介绍

       ThreadLocal 能够保证在多线程情况下,每个线程独有一份对象副本,线程之间的副本互相独立、隔离。

       在每个线程类Thread实例里拥有二份ThreadLocal.ThreadLocalMap, 一份为当前线程的threadLocals,另一份为当前线程的inheritableThreadLocals, 他们都是静态内部类,因此这2个thredLocalMap在堆里都只有一份。inheritableThreadLocals能够将当前主线程设置的变量值传给子线程的inheritableThreadLocals。

二、ThreadLocal核心方法解析

1) initialValue()

       用来初始化threadLocal的默认值, 默认为null。

protected T initialValue() {
    return null;
}		

2) setIntialValue()

       当set()方法被覆盖时,此方法替代set()方法的用法

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 获取当前线程的threadLocalMap, 如果为空那么createMap, 如果不为空,那么更新map的value值。
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
} 

3) set()

       给当前线程在ThreadLocalMap 设置一个value值,类型为自定义的T

public void set(T value) {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();
    // 2. 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 3. 查看map是否被初始化,如果被初始化了,那么覆盖掉之前的value, 否则初始化ThreadLocalMap
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

4) get()

     获取当前线程的value值。

    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();
    }

ThreadLocal是怎么做到多线程环境下副本隔离的?

 是通过ThreadLocalMap实现的。

三、ThreadLocalMap 源码解析

         ThreadLocalMap是ThreadLocal类里的静态内部类, 搞懂ThreadLocalMap就能拨开迷雾见光明。

  static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
}

跟着问题去探索ThreadLocalMap。

1) ThreadLocalMap是在什么时候初始化的呢?

       在调用set()方法时时候,然后会执行createMap。

 createMap时,会将线程和value都交给ThreadLocalMap。

2) Thread和value在ThreadLocalMap里是怎么存放的?

         每个线程存放的值是放在一个名为Entry静态内部类的value属性里,因此value为object类, 一个线程对应一个Entry, 在threadLocalMap类里有一个table, 该table就是用来存放所有线程(包含主线程和所有的子线程)的threadLocal的k和v, k为线程对象,v为object类型的value。

3) InheritablThreadLocal又是怎么存放的?

       如果是inheritableThreadLocal, 那么创建的map也会添加到ThreadLocalMap静态内部类的Entry[] table里,这样就能保证所有的线程(包含主线程和所有子线程) 都能够独有一份变量副本。

       经过一番调试后, 我发现所有的线程和副本最终都会放到ThreadLocalMap的Entry[] 里,这样就解释了每个线程之间的变量是独立的。

四、InheritableThreadLocal核心原理

1) inheritableThreadLocal究竟是何物?

       inheritableThreadLocal可将主线程设置的变量传到到子线程里。当不懂原理的时候,最好的方式就是debug源代码。

      子线程在初始化的时候,会进入到Thread类里的init()方法,然后会查看主线程的inheritableThreadLocal是否为空,如果不为空,那么就将主线程的inheritableThreadLocals复制一份给自己的inheritableThreadLocals。

 init()方法代码:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        ...
         // 将主线程的inheritableThreadLocals复制给子线程的inheritableThreadLocals
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

子线程的inheritableThreadLocals和主线程的一样,因此子线程也能拿到主线程设置的变量。

2) 案例演示

  private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();


    @Test
    void testThreadLocal() {
        inheritableThreadLocal.set(100);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getId() +  "_" + inheritableThreadLocal.get());
            }).start();
        }
    }

 打印结果:

以上是关于ThreadLocal和InheritablThreadLocal核心源码详解的主要内容,如果未能解决你的问题,请参考以下文章

深入剖析ThreadLocal

ThreadLocal 和内存泄漏

ThreadLocal介绍和应用

ThreadLocal 和 InheritableThreadLocal (引用)

ThreadLocal和异步

理解ThreadLocal(之二)