ThreadLocal
Don't forget, a person's greatest emotional need is to feel appreciated.
莫忘记,人类情感上最大的须要是感恩。java
在阅读Handler源码时发现了这么一个东西,本想直混在其余博客中一笔带过,但仔细想了下这个东西仍是蛮重要的,因而开了这篇博客。android
ThreadLocal
threadlocal使用方法很简单数组
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>(); sThreadLocal.set() sThreadLocal.get()
threadlocal而是一个线程内部的存储类,能够在指定线程内存储数据,数据存储之后,只有指定线程能够获得存储数据,官方解释以下。数据结构
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). */
大体意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不一样之处在于每个线程读取的变量是对应的互相独立的。经过get和set方法就能够获得当前线程对应的值。多线程
作个不恰当的比喻,从表面上看ThreadLocal至关于维护了一个map,key就是当前的线程,value就是须要存储的对象。app
这里的这个比喻是不恰当的,其实是ThreadLocal的静态内部类ThreadLocalMap为每一个Thread都维护了一个数组table,ThreadLocal肯定了一个数组下标,而这个下标就是value存储的对应位置。。ide
做为一个存储数据的类,关键点就在get和set方法。oop
//set 方法 public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //实际存储的数据结构类型 ThreadLocalMap map = getMap(t); //若是存在map就直接set,没有则建立map并set if (map != null) map.set(this, value); else createMap(t, value); } //getMap方法 ThreadLocalMap getMap(Thread t) { //thred中维护了一个ThreadLocalMap return t.threadLocals; } //createMap void createMap(Thread t, T firstValue) { //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue); }
从上面代码能够看出每一个线程持有一个ThreadLocalMap对象。每个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。this
Thread
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
Thread中关于ThreadLocalMap部分的相关声明,接下来看一下createMap方法中的实例化过程。atom
ThreadLocalMap
set方法
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用 //同时让ThreadLocal和储值造成key-value的关系 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //ThreadLocalMap构造方法 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //内部成员数组,INITIAL_CAPACITY值为16的常量 table = new Entry[INITIAL_CAPACITY]; //位运算,结果与取模相同,计算出须要存放的位置 //threadLocalHashCode比较有趣 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
经过上面的代码不难看出在实例化ThreadLocalMap时建立了一个长度为16的Entry数组。经过hashCode与length位运算肯定出一个索引值i,这个i就是被存储在table数组中的位置。
前面讲过每一个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法能够理解成每一个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是经过操做这个数组table完成的。
显然table是set和get的焦点,在看具体的set和get方法前,先看下面这段代码。
//在某一线程声明了ABC三种类型的ThreadLocal ThreadLocal<A> sThreadLocalA = new ThreadLocal<A>(); ThreadLocal<B> sThreadLocalB = new ThreadLocal<B>(); ThreadLocal<C> sThreadLocalC = new ThreadLocal<C>();
由前面咱们知道对于一个Thread来讲只有持有一个ThreadLocalMap,因此ABC对应同一个ThreadLocalMap对象。为了管理ABC,因而将他们存储在一个数组的不一样位置,而这个数组就是上面提到的Entry型的数组table。
那么问题来了,ABC在table中的位置是如何肯定的?为了能正常够正常的访问对应的值,确定存在一种方法计算出肯定的索引值i,show me code。
//ThreadLocalMap中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); //遍历tab若是已经存在则更新值 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; //知足条件数组扩容x2 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
在ThreadLocalMap中的set方法与构造方法能看到如下代码片断。
int i = key.threadLocalHashCode & (len-1)
-
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
简而言之就是将threadLocalHashCode进行一个位运算(取模)获得索引i,threadLocalHashCode代码以下。
//ThreadLocal中threadLocalHashCode相关代码. private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { //自增 return nextHashCode.getAndAdd(HASH_INCREMENT); }
由于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()方法
//ThreadLocal中get方法 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(); } //ThreadLocalMap中getEntry方法 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); }
理解了set方法,get方法也就清楚明了,无非是经过计算出索引直接从数组对应位置读取便可。
ThreadLocal实现主要涉及Thread,ThreadLocal,ThreadLocalMap这三个类。关于ThreadLocal的实现流程正如上面写的那样,实际代码还有许多细节处理的部分并无在这里写出来。
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不一样的点是
- Synchronized是经过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是经过每一个线程单独一份存储空间,牺牲空间来解决冲突,而且相比于Synchronized,ThreadLocal具备线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
正由于ThreadLocal的线程隔离特性,使他的应用场景相对来讲更为特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal。当某些数据是以线程为做用域而且不一样线程具备不一样的数据副本的时候,就能够考虑采用ThreadLocal。