Thread, ThreadLocal和ThreadLocalMap

279 阅读5分钟

关系

ThreadLocalMap是ThreadLocal的静态内部类,它也是Thread的成员变量

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

看一下demo

public void start(){
        ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
        threadLocal.set(true);

        Log.d(TAG, Thread.currentThread().getName() + " 的值是 " + threadLocal.get());
    **线程1**
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(false);
                Log.d(TAG, Thread.currentThread().getName() + " bool的值是 " + threadLocal.get());
                ThreadLocal<Integer> dd = new ThreadLocal<>();
                dd.set(55);
                Log.d(TAG, Thread.currentThread().getName() + " int的值是 " + dd.get());
            }
        }).start();
   **线程2**
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, Thread.currentThread().getName() + " 的值是 " + threadLocal.get());
            }
        }).start();

在主线程定义一个threadLocal变量

  1. 在主线程中设置true,
  2. 在子线程1中设置false,然后又new一个新的对象,设置55
  3. 在子线程2中没有操作,直接获取 查看打印的结果

看到主线程、子线程1和子线程2的结果互不影响。 所以ThreadLocal的主要作用就是保存线程的变量,作用域为线程。

分析

1. 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);
    }

以子线程1为例,那么首先获取子线程1,然后调用getMap(t)方法,

  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

因为我们创建线程的时候并没有初始化threadLocals,所以返回null,那么根据上面的判断,

  • 如果返回null,则会调用createMap(t, value)
 /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

这里分2步

  1. 创建ThreadLocalMap对象
  2. 把对象指给当前线程的threadLocals,那么下次就不用再new了,这里采用的是懒加载的方式。 我们来看一下ThreadLocalMap的创建。
 /**
         * 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);
        }
        private Entry[] table;
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

这里又做了以下几件事

  1. 创建一个类型为Entry的数组,table,初始值为16。Entry是内部类,继承弱引用,
  2. 根据将ThreadLocal这个对象进行hash处理,获得下标i。
  3. 以ThreadLocal为对象,传入的值为value,本例中也就是false,创建Entry对象,放入下标为i的table数组中,设置size = 1
  4. 设置阈值,这个阈值为了以后的扩容用。
  • 如果map不为null,也就是在demo的线程1中调用第二个set,则调用map.set(this, value);
/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

以上步骤可以总结为

  1. 遍历tab数组,如果之前的key有值,那么直接替换为新的value,如果key为null,则
  2. 遍历tab数组完没有找到,则新建一个Entry放入中,下标为i,同时size+1,然后判断如果没有废弃的并且当前的个数打印阈值,则进行扩容,新容量为旧容量的2倍 那么现在set的方法原理就讲完了。

2. 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();
    }
  1. 获取当前线程的ThreadLocalMap,那么根据set的分析,现在肯定是不为null的
  2. 查看map.getEntry(this)
 /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

根据当前的ThreadLocal计算出下标,然后根据下标查找table,

  1. 如果不为null,也就是demo中的线程1,并且key也符合,则返回Entry,然后返回Entry的value,也就是所需要的值。
  2. 如果为null,也就是demo中的线程2(因为线程2中没有set值),则调用getEntryAfterMiss方法,辞职的e为null
 /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

因为传入的e为null,所以while循环不执行,直接返回null,也就是我们打印的结果。

总结

1个Thread对应一个ThreadLocalMap,ThreadLocalMap中有个类型为Entry为的table数值,采用懒加载的方式,当调用set方法时才初始化,Entry的key为ThreadLocal对象,所以Thread 和 ThreadLocal是一对多的关系。table的下标是ThreadLocal对象的哈希码与上table容量的结果,get时也是这么计算,然后在table中寻找的。