聊聊ThreadLocal:

##前言

  • 在之前的文章里,Looper、Handler等等,我们都对源码进行了解析,只剩下最后一个疑问点,那就是Looper的存放。
  • 我们能看到,Looper被创建出来以后,是被放到ThreadLocal内部进行存储的。这个ThreadLocal到底是何方神圣呢?

##Looper中的使用:

  • 其实ThreadLocal在Looper中的使用很简单,Looper内部有一个static变量sThreadLocal,所有的调用都与它有关:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//ThreadLocal初始化

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));//存储变量到ThreadLocal中
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();//获取ThreadLocal中的变量
}
  • 从代码含义上看,ThreadLocal是一个存储变量,类似List一样的容器。但实际上,它还有更胜一层的意义。

#含义

  • ThreadLocal用来提供线程的局部变量,能保证这个变量在每一个Thread中,都是一个单独的副本。怎么理解呢?在Thread内部,它是独一份的,并且在另一个线程中的同名变量,他们是不会互相干扰的。
  • 简单说来,你可以理解成,这是一个Thread内部的全局变量,这个全局变量的访问域,就是在Thread内部,出去了,就访问不到了。
  • ThreadLocal就是为了保证在多线程环境下,创建存储需要独立访问的数据而诞生的。

基于线程独立的存在:

public static void main(String[] args) {

    Thread threadA = new Thread() {
        @Override
        public void run() {
            super.run();
            Test.getThreadLocal().set("threadA");
            System.out.println("Thread A put ThreadLocal into A");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread A :" + Test.getThreadLocal().get());
        }
    };

    Thread threadB = new Thread() {
        @Override
        public void run() {
            super.run();
            Test.getThreadLocal().set("threadB");
            System.out.println("Thread B put ThreadLocal into B");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread B :" + Test.getThreadLocal().get());
        }
    };
    threadA.start();
    threadB.start();
}

public static class Test {

    static final ThreadLocal<String> sThreadLocal = new ThreadLocal<String>();

    public static ThreadLocal<String> getThreadLocal() {
        return sThreadLocal;
    }
}
  • 上述代码,就是一个简单的多线程ThreadLocal存储数据的模型,我们可以看到ThreadA和ThreadB分别存放了自己的变量,在sleep一段时间后取出,执行结果如下:

    Thread B put ThreadLocal into B Thread A put ThreadLocal into A Thread A :threadA Thread B :threadB

  • 由上述代码可以看出,ThreadLocal相当于一个容器,存放我们通过泛型指定要存储的数据

  • 而且确确实实就是根据不同的Thread,存放的不同数据,不会产生相互干扰。

#原理

  • 说一千道一万,不如掏出源码看一看。关于ThreadLocal的作用我们已经知道了。接下来,我们再研究下它的实现原理吧。

Read The Fucking Source Code!

  • 扒开ThreadLocal的源码,我们能看到构造器里,ThreadLocal其实什么也没有,那我们就从set()函数入手:
    public void set(T value) {
        Thread t = Thread.currentThread();//每次调用set()函数,都会先取出当前调用的线程。看样子区分线程独立,就是以此为原理的
        ThreadLocalMap map = getMap(t);//同时TheadLocal里,设置了一个Map,我们一会儿看下里面做了什么得到了一个ThreadLocalMap对象
        if (map != null)
            map.set(this, value);//取到ThreadLocalMap,就直接set数据
        else
            createMap(t, value);//没取到的话,就用当前线程创建一个ThreadLocalMap
    }
  • 代码不多也不复杂,不外乎就是创建一个ThreadLocalMap容器嘛,我们先来看看createMap():
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);//new了一个ThreadLocalMap,塞到了调用线程的threadLocals成员里
    }
  • 很明了了,这应该是Java的特性之一,专门提供了一个ThreadLocalMap数据结构,针对性地存储这些需要基于线程独立存在的数据变量。
  • 它以ThreadLocal(this)对象作为key,将开发者需要存储的value进行hash映射存放了起来。
  • 我们可以看一下ThreadLocalMap的构造器:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//创建一个Entry数组
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//根据ThreadLcoal中的threadLocalHashCode变量为基准,生成存储的index
    table[i] = new Entry(firstKey, firstValue);//存储数据
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
  • 再来看一眼Entry是什么结构:
        static class Entry extends WeakReference<ThreadLocal<?>> {//继承了WeakReference
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  • 很明显,Entry就是一个弱引用对象来持有需要存储的数据。

##总结

  • ThreadLocal其实是一个Thread提供的数据结构,以用户指定的泛型为基准,存储每一个Thread独立的数据结构。
  • 底层原理,是数组加弱引用进行存储数据,索引是根据ThreadLocal对象的threadLocalHashCode为基准进行计算的。
  • 至于这个threadLocalHashCode是怎么计算的,简单说来就是一个指定的基准,进行hash运算的法则。有兴趣的同学可以自己看下,就不深究了。

 评论