ThreadLocal<T> 源码解析

2019-11-18 来源: 靳刘杰 发布在  https://www.cnblogs.com/theone67/p/11884581.html

在activeJDBC框架内部的实现中看到了 ThreadLocal 这个类,记录下了每个线程独有的连接

private static final ThreadLocal<HashMap<String, Connection>> connectionsTL = new ThreadLocal<>();

感觉是个知识点,就打开源码看看了。先看一下源码里的解释

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

这个鸟文,瞎翻译一下,就是:

这个类提供了供线程专享的变量。这些变量不同与其它普通的变量,它是每个线程都有一个自己的独立初始化的变量(通过get和set方法实现)。这个类的实例常用于类的私有静态字段,以实现每个线程都有自己的状态(例如userId,事务ID等)。

先跑一下用法吧,

package com.test.threadlocal;

public class TestController {

    private static int index = 0;
    private static String str = "这个字符串是每个线程共享的";
    // 这个变量,看似是一个类的静态属性,实则是每个线程有自己独有的区域
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "main线程专享";
        }
    };      

    public static void main(String[] args) throws InterruptedException {
        for(int i = 0; i < 3; i++) {
            Thread t = new MyThread();
            t.start();
            t.join();
        }

        System.out.println(str);
        System.out.println(threadStr.get());
    }

    static class MyThread extends Thread{

        @Override
        public void run() {
            index++;
            str = "第" + index + "个str";
            threadStr.set("第" + index + "个threadStr");
        }

    }

}

这个例子中,从str和threadStr变量的打印结果可以看出来。str被所有的线程读和写,threadStr在每个线程内部开辟了一块线程专享的区域。接下来,我们看一下具体实现。
先看一下构造函数

     /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();
     /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

构造函数是空的,但是,该类有一个私有整型常量threadLocalHashCodenextHashCode()方法我们就不看了,省的一如源码深似海。看鸟文的话,大概就是每new一个ThreadLocal变量的时候,就会生成一个散列码,该码非极端情况下与某个整数取模后不容易冲突(这句话有点迷吧,其实我也不懂)
然后看一下set方法

/**
     * 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);
    }

容易看出,这个方法设置每个线程自己的value,相当于当前线程是key,然后得出一个ThreadLocalMap。显然,这个map用来保存线程内部的值,既然是map当然每个线程可以保存多个数值了,该map的value我们猜一下就是我要保存的具体的值,估计是用Object类声明的。那key是什么呢?我们看下ThreadLocalMap类的构造方法。

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

/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        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);
        }

我的天!这个类没有继承我们想象中的HashMap,或者是ConcurrentMap,但是看过,Map的内部实现的同学应该可以发现,这个Map的实现和HashMap的实现简直就是小巫见大巫,有没有。它在构造函数中做了如下几步:

  1. 初始化一个大小为16的Entry数组
  2. 通过上面说过的很迷的HashCode散列值与15取模得到将要存储在数组中的索引值
  3. 构造Entry,然后保存进去
  4. 长度设置为1
  5. 设置要扩容的限制大小为16的2/3

我们看到这个不就是用数组实现的Map嘛,看过HashMap实现的我们,觉得洒洒水啦。
Map的set和get方法就不分析了。ThreadLocal的get方法我们还是要贴出来的,毕竟是我们主要分析的东西

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

可见,是获取到当前线程,用作key获取到Map,然后用当前this获取到Entry实体。最后当然获取到了存储的value。

我编码,我快乐~

本文由博客一文多发平台 OpenWrite 发布!

相关文章