ThreadLocal
Don’t forget, a person’s greatest emotional need is to feel appreciated.
莫忘记,人类情感上最大的需要是感恩。
在阅读Handler源码时发现了这么一个东西,本想直混在其他博客中一笔带过,但仔细想了下这个东西还是蛮重要的,于是开了这篇博客。
ThreadLocal
threadlocal使用方法很简单
1 | static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>(); |
threadlocal而是一个线程内部的存储类,可以在线程内存储数据,数据存储以后,只有存储的线程可以取到被存储数据,其他线程获取则为缺省值 ,官方解释如下。
1 | /** |
大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
这一切的关键点都是Thread内的变量threadLocals,类型为ThreadLocalMap。
作为一个存储数据的类,关键点就在get和set方法。
1 | //set 方法 |
从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
假设有ThreadLocal sample= new ThreadLocal();
从上面代码不难看出在不同线程访问sample的get/set方法时,操作的是对应线程的ThreadLocalMap,是不同的实例对象。
这就是数据隔离的原因。
Thread
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
Thread中关于ThreadLocalMap部分的相关声明,接下来看一下createMap方法中的实例化过程。
ThreadLocalMap
1 | //Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用 |
在ThreadLocalMap中的set方法与构造方法能看到以下代码片段。
int i = key.threadLocalHashCode & (len-1)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
简而言之就是将threadLocalHashCode进行一个位运算(取模)得到索引i,
不同的ThreadLocal对象是存储在tab数组中的不同position,position的计算和threadLocalHashCode有关。
threadLocalHashCode对于实例化后的ThreadLocal是固定且唯一的,也就是说每个ThreadLocal实例对应的position是固定唯一的。
到这里能发现如下两点:
- 对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程里set/get时操作的是每个线程自己的数组Entry[] 。
- 对于同一线程的不同ThreadLocal来讲,这些ThreadLocal存储在同一个数组Entry[],只是他们存储的position不同。
threadLocalHashCode代码如下。
1 | public class ThreadLocal<T> { |
每次实例化ThreadLocal,就会初始化threadLocalHashCode,从而调用静态方法nextHashCode,达到hash值递增的目的,增量为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 |
get()方法
1 | //ThreadLocal中get方法 |
理解了set方法,get方法也就清楚明了,无非是通过计算出索引直接从数组对应位置读取即可。
ThreadLocal实现主要涉及Thread,ThreadLocal,ThreadLocalMap这三个类。关于ThreadLocal的实现流程正如上面写的那样,实际代码还有许多细节处理的部分并没有在这里写出来。
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。