ThreadLocal 源码简介

简单介绍线程内部存储数据存储类 ThreadLocal

ThreadLocal的诞生或者说ThreadLocal要解决的问题

有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口 
的多样性, 在这种情况下我们又需要监听器能够贯穿整个线程的执行过程,这个时候 
可以怎么做呢?其实这时就可以采用ThreadLocal,采用ThreadLocal可以让监听器 
作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。如
果不采用ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监
听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量
 供线程访问。上述这两种方法都是有局限性的。 第一种方法的问题是当函数调用栈很
看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如同
时 有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线程在并
发执 呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal,
每个 监听器对象都在自己的线程内部存储,根本就不会有方法2的这种问题。

ThreadLocal 使用案例:

public class MyClass {

    private static ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
    public static void main(String[] args){
        System.out.println("main");
        //向线程#Main中存储一个true值
        mBooleanThreadLocal.set(true);

        System.out.println("main#" +mBooleanThreadLocal.get());
        test();
    }

    public static void test(){

        new Thread("Thread#1"){
            @Override
            public void run() {
                //向线程thread#1中存储一个false值
                mBooleanThreadLocal.set(false);
                System.out.println("[Thread#1]"+mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2"){
            @Override
            public void run() {
                 //打印thread#2中存储的默认值
                System.out.println("[Thread#2]"+mBooleanThreadLocal.get());
            }
        }.start();
    }
}

运行结果为:

main
main#true
[Thread#2]null
[Thread#1]false

可以看出在主线程中ThreadLocal 存储的是true,Thread#1线程中存储的为false,Thread#2线程存储的为null。
让我们来看看是怎么回事

源码解析

查看Thread.java 可以看到存在一个成员变量

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

这个成员变量就是用来保存线程运行期间的全局数据的。
让我们来看看 ThreadLocalMap 是一个什么东西,看官方介绍ThreadLocalMap是一个自定义的hashmap.

那我们看看这个threadLocals 是什么时候初始化的

    void createMap(Thread t, T firstValue) {
         //初始化当前线程的ThreadLocalMap成员变量
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

让我们再看看这个方法什么时候被调用的

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
           //初始化
            createMap(t, value);
    }

可以看到这个方法就是我们在例子中mBooleanThreadLocal调用的方法

再来看看是怎样取数据的,也是我们调用mBooleanThreadLocal.get()方法做了什么事

  public T get() {
         //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals 实例对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //获取到对应的Entry,key为当前的LocalThread对象 value为我们存储的值。
            //这个时候我们应该思考一下是否会存在内存泄漏的问题
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

关于ThreadLocal的内存泄漏风险参考
结论:为了避免内存泄漏我们最好 调用一下ThreadLocal提供的remove方法

 mBooleanThreadLocal.remove();

ThreadLocalMap源码简单分析

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
     大概意思就是ThreadLocalMap里面是一个自定义的HashMap,这个数据结构里面存储的值是当前线程私有的
     供这个线程全局使用,用于存储一些生命周期比较长的值,这个数据结构里面的保存这个一个Entry对象key是使用
     WeakReferences 包裹着,保证其能及时的释放。
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

在ThreadLocalMap中存在一个成员变量table

 private Entry[] table;

初始化:

 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 void set(ThreadLocal<?> key, Object value) {
            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();
        }

总结:这符合Java的万物皆对象的思想,线程也是一个类,new Thread()也是在创建了一个线程的实例对象,就像我们平常创建的一个自定义View实例一样,有时候我们为了分层让代码便于维护会在这个自定义的View中维护一个集合或者数组,如果外部数据有变化,通过我们对外暴露的方法更新数据源,这个ThreadLocal就相当于自定义View中的数据源,这是java封装性的一种思想。

猜你喜欢

转载自blog.csdn.net/lovebuzhidao/article/details/79749119