ThreadLocal是什么?
Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values. 实现了每个线程的自有变量的存储。所有线程共享同一个ThreadLocal对象,但是每个线程只能访问自己所存储的变量,并且线程之间做的改动互不影响。此实现支持null变量的存储。(非逐词翻译)
通过这个描述,我们可以知道一些信息:
ThreadLocal是一个线程的内部存储类,通过它我们可以在指定的线程中存储信息。
每个线程之间的信息是独立且封闭的。
ThreadLocal的用途
ThreadLocal的作用是实现每个线程的自有变量的存储,这个“自有变量”具体是什么当然要根据不同的需求来定,但是在Android的源码中,这个自有变量常常是Looper。
这个Looper是什么呢?这里就不得不提到Android的消息机制了。Android的消息机制是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。简单来讲,就是Handler发送Message到MessageQueue中,而Looper不断的从MessageQueue中循环摘取Message,并进行进一步的解析处理。
但是Looper只是个简单的类而已,它虽然提供了循环处理方面的成员函数loop(),却不能自己凭空地运行起来,而只能寄身于某个真实的线程。那么Looper是怎么和线程建立联系的呢?这个时候ThreadLocal就参与其中了。
我们来看一下Looper的部分源码:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException(“Only one Looper may be created per thread”); } //调用ThreadLocal的set()方法将Looper存进去 sThreadLocal.set(new Looper(quitAllowed)); } public static void loop() { //调用myLooper()方法得到Looper,我们接着看一下myLooper()方法 final Looper me = myLooper(); if (me == null) { throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”); } …… …… } public static @Nullable Looper myLooper() { //调用ThreadLocal的get()方法得到刚才存入的Looper对象 return sThreadLocal.get(); }
ThreadLocal用法
它的用法其实挺简单的,暴露出来的方法一共只有三个:
get():返回调用线程中变量的当前值
set(T value):设置调用线程中变量的值
remove():移除调用线程的当前变量
用的话也是这三个方法,就跟上面Looper里面的用法差不多。例子的代码如下:
mIntegerThreadLocal = new ThreadLocal<>(); mIntegerThreadLocal.set(0); //在主线程里设置mIntegerThreadLocal的值为0,输出0 Log.d(TAG, “[Thread#main]mIntegerThreadLocal=” + mIntegerThreadLocal.get()); new Thread(“Thread#1”) { @Override public void run() { //设置mIntegerThreadLocal的值为1,输出1 mIntegerThreadLocal.set(1); Log.d(TAG, “[Thread#1]mIntegerThreadLocal=” + mIntegerThreadLocal.get()); } }.start(); new Thread(“Thread#2”) { @Override public void run() { //不设置任何值,输出null Log.d(TAG, “[Thread#2]mIntegerThreadLocal=” + mIntegerThreadLocal.get()); } }.start(); new Thread(“Thread#3”) { @Override public void run() { //设置为3,然后remove,输出null mIntegerThreadLocal.set(3); mIntegerThreadLocal.remove(); Log.d(TAG, “[Thread#3]mIntegerThreadLocal=” + mIntegerThreadLocal.get()); } }.start();
源码解析
先看一下它的构造方法:
public ThreadLocal() {}
可以的,啥都没干,空构造,这条路子走不通。
暴露方法
public void set(T value)
接下来从暴露出来的方法入手,先看set()方法:
public void set(T value) { //Thread.currentThread()方法是一个native的方法,功能是返回调用这个方法的线程 Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
Values
native的方法咱们先不看,知道他是返回调用线程就好了,直接看第二行。第二行出现了一个之前没见过的类:Values。接下来找找Values是何方神圣:
static class Values { /** * 大小是2的n次方 */ private static final int INITIAL_SIZE = 16; /** * 被删除的数据的占位符,因为是用object数组查询的数据,所以需要这个 */ private static final Object TOMBSTONE = new Object(); /** * 存放数据的数组。他存放数据的形式有点像map,是ThreadLocal与value相对应 * * 长度总是2的N次方 */ private Object[] table; /** 用来将hash地址转换为索引 */ private int mask; /** 当前存活数据的数目 */ private int size; /** 当前失效数据的数目 */ private int tombstones; /** 允许的存活数据与失效数据数目总和的最大值 */ private int maximumLoad; /** 指向下一个要清理的数据 */ private int clean;
原来Values是ThreadLocal的一个静态的内部类,它的功能就是存储放进来的数据以及对数据进行处理,比如清理失效数据啦,扩展存储的table啦,等等。它存储数据是用的Object数组,并且有一些值来记录当前存活的数据以及失效数据的数目,通过这些数据它可以及时的清理无效的数据并且扩展或者缩小当前table的大小,便于保持当前table的健壮性。
由于这个内部类还是很重要的,我们再往里面挖掘一下它里面的源码,看看到底是怎么一卵回事:
可以看到,这里面有俩构造方法:
Values()
Values(Values values)
并且有四个暴露出来的方法:
– void put(ThreadLocal> key, Object value):往table里添加一个键值对 – void add(ThreadLocal> key, Object value):也是往table里面添加键值对,但是比起put()来少了很多繁琐但是很棒的操作,下面再详述 – Object getAfterMiss(ThreadLocal> key):在首位置没找到值的时候通过这个方法来找到给定key的值 – void remove(ThreadLocal> key):删掉给定key对应的值
构造方法
Values() { //初始化table initializeTable(INITIAL_SIZE); this.size = 0; this.tombstones = 0; } Values(Values fromParent) { this.table = fromParent.table.clone(); this.mask = fromParent.mask; this.size = fromParent.size; this.tombstones = fromParent.tombstones; this.maximumLoad = fromParent.maximumLoad; this.clean = fromParent.clean; inheritValues(fromParent); }
这两个构造方法一个是普通的构造,一个是类似于继承的那种,从一个父Values对象来生成新的Values,我们先看普通的这个构造。它就只是做了一些常规的初始化操作,initializeTable(int size)方法是这样的:
private void initializeTable(int capacity) { //新建values里面的table,默认大小是32 this.table = new Object[capacity * 2]; //mask的默认大小是table的长度减一,也就是table中最后一个元素的索引 this.mask = table.length – 1; this.clean = 0; //默认的最大load值是capacity的2/3,其实我不是很能理解这个值是怎么得来的 this.maximumLoad = capacity * 2 / 3; // 2/3 }
void add(ThreadLocal
void add(ThreadLocal> key, Object value) { for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == null) { //在index处放入一个键 table[index] = key.reference; //在index的下一位放入键对应的值 table[index + 1] = value; return; } } }
上面的代码向我们展示了table存储数据的方式,它是以一种类似于map的方式来存储的,在index处存入map的键,在index的下一位存入键对应的值,而这个键则是ThreadLocal的引用,这里毫无问题。但是有一个地方问题则是大大的有: int index = key.hash & mask 。大家都明白这行代码的作用是获得可用的索引,可是到底是怎么获得的呢?为什么要通过这种方式来获得呢?要解决这个问题,我们要先知道何为“可用的索引”,通过分析观察,我总结出了一些条件:
要是偶数。(这个很显然)
不能越界。(都比table的边界长了那还了得)
尽可能分散。(尽可能的新产生的索引不要是已经出现过的数,不然table的空间不能充分的利用,而且观察上面代码,会发现如果新产生的索引是已经出现过的数的话数据根本存不进去)
void put(ThreadLocal
void put(ThreadLocal> key, Object value) { cleanUp(); //标志第一个失效键的index int firstTombstone = -1; //获取当前index for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; //如果键相同,就用传进来的值替换原来的值 if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } //如果键为空,就放入传进来的键值 if (k == null) { //firstTombstone == -1表示firstTombstone还没有改变过,即循环到现在还没有发现失效键 if (firstTombstone == -1) { //放入传进来的键值 table[index] = key.reference; table[index + 1] = value; size++; return; } // 如果firstTombstone != -1,说明上一个循环中遇到了失效键,那么就将放入传进来的键值放入那个失效键值的位置 table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones–; size++; return; } // 如果当前键为失效键,则标记其位置,准备在其位置放入传入的键值对 if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }
可以看到,put()方法里面的逻辑其实很简单,就是在想方设法的把传进来的键值对给存进去——其中对获得的index的值进行了一些判断,以决定如何进行存储——总之是想要尽可能的节省空间。另外,值得注意的是,在遇到相同索引处存放着同一个键的时候,其采取的方式是新值换旧值。除此之外,我们可以看到,在方法的第一行其调用了另一个方法cleanUp(),这个方法又是干嘛的呢?
private void cleanUp() { if (rehash()) { // 如果rehash()方法返回的是true,就不需要继续clean up了 return; } //如果table的size是0的话,也不需要clean up. //都没有键值在里面有什么好clean的? if (size == 0) { return; } // 默认的clean是0. //下一次再clean的时候是从上一次clean的地方继续 int index = clean; Object[] table = this.table; for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) { Object k = table[index]; //如果键是TOMBSTONE或者null的话,就可以下一次循环了 if (k == TOMBSTONE || k == null) { continue; } // 这个table只有可能存储null, tombstones 和 references的键. @SuppressWarnings(“unchecked”) Reference
cleanUp()方法只做了一件事,就是把失效的键放上TOMBSTONE去占位,然后释放它的值。那么rehash()是干什么的其实已经很显而易见了:
从字面意思来也知道是重新规划table的大小。
联想cleanUp()的作用,它都已经把失效键放上TOMBSTONE,接下来呢?显然是想办法干掉这些TOMBSTONE,还我内存一个朗朗乾坤喽。
Object getAfterMiss(ThreadLocal
public T get() { // 获得调用线程 Thread currentThread = Thread.currentThread(); //获得调用线程的Values Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; //如果在当前index处的键不是此线程的引用,就会去调用getAfterMiss() if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }
它的作用就是在第一个位置没有找到此键对应的值的时候继续查询,达到获得其值的目的。但是在看它的代码的过程中,我整个人是懵逼的。为什么呢?按照我的想法,没有在第一个位置得到这个调用线程存储的信息之后,应该下一步是一个一个index的循环这个table,来找到调用线程存储的信息,如果实在找不到,就说明调用线程没有存储信息在里面,那么就应该初始化一个value给它——当然,这个初始化value的方法理应是可以重写的。初始化之后,就是用一种巧妙的方法把它存进table里面,然后返回这个value了。
可是,它的源码根本不是这样!
Object getAfterMiss(ThreadLocal> key) { Object[] table = this.table; int index = key.hash & mask; // If the first slot is empty, the search is over. if (table[index] == null) { Object value = key.initialValue(); // If the table is still the same and the slot is still empty… if (this.table == table && table[index] == null) { table[index] = key.reference; table[index + 1] = value; size++; cleanUp(); return value; } // The table changed during initialValue(). put(key, value); return value; } // Keep track of first tombstone. That’s where we want to go back // and add an entry if necessary. int firstTombstone = -1; // Continue search. for (index = next(index);; index = next(index)) { Object reference = table[index]; if (reference == key.reference) { return table[index + 1]; } // If no entry was found… if (reference == null) { Object value = key.initialValue(); // If the table is still the same… if (this.table == table) { // If we passed a tombstone and that slot still // contains a tombstone… if (firstTombstone > -1 && table[firstTombstone] == TOMBSTONE) { table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones–; size++; // No need to clean up here. We aren’t filling // in a null slot. return value; } // If this slot is still empty… if (table[index] == null) { table[index] = key.reference; table[index + 1] = value; size++; cleanUp(); return value; } } // The table changed during initialValue(). put(key, value); return value; } if (firstTombstone == -1 && reference == TOMBSTONE) { // Keep track of this tombstone so we can overwrite it. firstTombstone = index; } } }
void remove(ThreadLocal
void remove(ThreadLocal> key) { //先把table清理一下 cleanUp(); for (int index = key.hash & mask;; index = next(index)) { Object reference = table[index]; //把那个引用的用TOMBSTONE占用 if (reference == key.reference) { // Success! table[index] = TOMBSTONE; table[index + 1] = null; tombstones++; size–; return; } if (reference == null) { // No entry found. return; } } }
结语
分析到这里,整个ThreadLocal的源码就分析的差不多了。在这里我们简单的总结一下这个类:
这个类之所以能够存储每个thread的信息,是因为它的内部有一个Values内部类,而Values中有一个Object数组。
Object数组是以一种近似于map的形式来存储数据的,其中偶数位存ThreadLocal的弱引用,它的下一位存值。
在寻址的时候,Values采用了一种很神奇的方式——斐波拉契散列寻址
Values里面的getAfterMiss()方法让人觉得很奇怪
参考链接
由浅入深全面剖析ThreadLocal – lypeer的专栏 – 博客频道 – CSDN.NET
Android消息机制与ThreadLocal
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue的中文翻译是消息队列,顾名思义它的内部存储了一组消息,其以队列的形式对外提供插入和删除的工作,虽然叫做消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。Looper的中文翻译为循环,在这里可以理解为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
参考:Android的消息机制之ThreadLocal的工作原理 – 任玉刚 – 博客频道 – CSDN.NET