the good goal will make you happy and fightful.
今日电话面试阿里时,突然问到了ThreadLocal.一时没有想起来。。
threadlocal使用方法很简单
1 | dart复制代码static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>(); |
threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,官方解释如下。
1 | php复制代码/** |
大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
做个不恰当的比喻,从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。
这里的这个比喻是不恰当的,实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。。
作为一个存储数据的类,关键点就在get和set方法。
1 | scss复制代码//set 方法 |
从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
Thread
1 | java复制代码/* ThreadLocal values pertaining to this thread. This map is maintained |
Thread中关于ThreadLocalMap部分的相关声明,接下来看一下createMap方法中的实例化过程。
ThreadLocalMap
set方法
1 | scala复制代码//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用 |
通过上面的代码不难看出在实例化ThreadLocalMap时创建了一个长度为16的Entry数组。通过hashCode与length位运算确定出一个索引值i,这个i就是被存储在table数组中的位置。
前面讲过每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法可以理解成每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
显然table是set和get的焦点,在看具体的set和get方法前,先看下面这段代码。
1 | ini复制代码//在某一线程声明了ABC三种类型的ThreadLocal |
由前面我们知道对于一个Thread来说只有持有一个ThreadLocalMap,所以ABC对应同一个ThreadLocalMap对象。为了管理ABC,于是将他们存储在一个数组的不同位置,而这个数组就是上面提到的Entry型的数组table。
那么问题来了,ABC在table中的位置是如何确定的?为了能正常够正常的访问对应的值,肯定存在一种方法计算出确定的索引值i,show me code。
1 | ini复制代码 //ThreadLocalMap中set方法。 |
在ThreadLocalMap中的set方法与构造方法能看到以下代码片段。
int i = key.threadLocalHashCode & (len-1)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
简而言之就是将threadLocalHashCode进行一个位运算(取模)得到索引i,threadLocalHashCode代码如下。
1 | arduino复制代码 //ThreadLocal中threadLocalHashCode相关代码. |
因为static的原因,在每次new ThreadLocal时因为threadLocalHashCode的初始化,会使threadLocalHashCode值自增一次,增量为0x61c88647。
0x61c88647是斐波那契散列乘数,它的优点是通过它散列(hash)出来的结果分布会比较均匀,可以很大程度上避免hash冲突,已初始容量16为例,hash并与15位运算计算数组下标结果如下:
hashCode 数组下标
0x61c88647 7
0xc3910c8e 14
0x255992d5 5
0x8722191c 12
0xe8ea9f63 3
0x4ab325aa 10
0xac7babf1 1
0xe443238 8
0x700cb87f 15
总结如下:
对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。
get()方法
1 | java复制代码//ThreadLocal中get方法 |
理解了set方法,get方法也就清楚明了,无非是通过计算出索引直接从数组对应位置读取即可。
ThreadLocal实现主要涉及Thread,ThreadLocal,ThreadLocalMap这三个类。关于ThreadLocal的实现流程正如上面写的那样,实际代码还有许多细节处理的部分并没有在这里写出来。
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
Synchronized是通过线程等待,牺牲时间来解决访问冲突
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
本文转载自: 掘金