Android 异步消息处理机制前篇(一):深入理解ThreadLocal

版权声明:本文出自汪磊的博客,转载请务必注明出处。

ThreadLocal简介

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。比如:我们在子线程A中存储数据x,只有在子线程中取数据才会获取存储的数据x,在B线程中获取是获取不到的。存储与获取数据都是要在同一线程中操作。

关于ThreadLocal的使用这里就不举例了(PS:其实是TMD简单了),我们直接分析存储,获取数据核心源码

安卓中ThreadLocal存储数据核心源码分析

ThreadLocal中存储数据调用的是set(T value)方法(T是什么?泛型),源码如下:

1 public void set(T value) {
2         Thread currentThread = Thread.currentThread();
3         Values values = values(currentThread);
4         if (values == null) {
5             values = initializeValues(currentThread);
6         }
7         values.put(this, value);
8 }

第2行:获取当前线程currentThread,如果在主线程调用set方法,则为主线程,如果在子线程调用则为对应的子线程。

第3行:调用values方法获取Values对象values(PS:这句话挺拗口),看下values方法:

1  Values values(Thread current) {
2         return current.localValues;
3 }

很简单,就是返回第2行代码获取的currentThread中的localValues,Thread类中定义了一个ThreadLocal.Values类型的变量localValues。

回到set(T value)方法继续看4-6行:如果values为null则对其进行初始化,第一次调用set方法的时候这里肯定为空,我们看下初始化initializeValues方法:

1 Values initializeValues(Thread current) {
2         return current.localValues = new Values();
3  }

同样很简单,就是对我们获取的当前线程中localValues变量进行初始化。

第7行:调用values的put方法进行数据的存储。

Values是什么鬼呢?怎么最终调用的是Values的put方法?其实Values是ThreadLocal的内部类,内部维护一个Object类型的数组table,我们存储的数据都是存储在内部数组中的,同样获取数据也是调用的Values类中相应方法。 接下来我们分析一下Values类。

ThreadLocal中内部类Values源码分析

先从构造方法看看吧:

 1 //数组初始化大小
 2 private static final int INITIAL_SIZE = 16;
 3 //存储数据的数组
 4 private Object[] table;
 5 //用于计算存储数据的下标index,长度为table.length - 1,可以防止角标越界
 6 private int mask;
 7 //记录数组中数据数量
 8 private int size;
 9 
10 Values() {
11           initializeTable(INITIAL_SIZE);
12             this.size = 0;
13             this.tombstones = 0;
14  }
15 
16 private void initializeTable(int capacity) {
17             this.table = new Object[capacity * 2];
18             this.mask = table.length - 1;
19             this.clean = 0;
20             this.maximumLoad = capacity * 2 / 3; // 2/3
21  }

这里就没什么要说的了,就是一些初始化方法。接下来我们看下存储数据的方法put(ThreadLocal<?> key, Object value)源码:

 1 void put(ThreadLocal<?> key, Object value) {
 2             cleanUp();
 3 
 4             // Keep track of first tombstone. That's where we want to go back
 5             // and add an entry if necessary.
 6             int firstTombstone = -1;
 7 
 8             for (int index = key.hash & mask;; index = next(index)) {
 9                 Object k = table[index];
10 
11                 if (k == key.reference) {
12                     // Replace existing entry.
13                     table[index + 1] = value;
14                     return;
15                 }
16 
17                 if (k == null) {
18                     if (firstTombstone == -1) {
19                         // Fill in null slot.
20                         table[index] = key.reference;
21                         table[index + 1] = value;
22                         size++;
23                         return;
24                     }
25 
26                     // Go back and replace first tombstone.
27                     table[firstTombstone] = key.reference;
28                     table[firstTombstone + 1] = value;
29                     tombstones--;
30                     size++;
31                     return;
32                 }
33 
34                 // Remember first tombstone.
35                 if (firstTombstone == -1 && k == TOMBSTONE) {
36                     firstTombstone = index;
37                 }
38             }
39  }

第6行:firstTombstone 用于记录已经删除的table数组中数据角标,严格说是key的角标,如果有被删除的数据则存储数据的时候在当前位置直接插入即可,节省内存啊。别急,分析完你会理解的。

8-38行遍历table数组中已经存储的数据与将要存储的数据比较,不同情况进行不同的处理(PS:这尼玛不是废话吗)

第8行:int index = key.hash & mask 算出存储数据key的索引,key就是当前的ThreadLocal,还记得这个mask吗?初始化时设置长度为table.length - 1,进行按位与操作计算出的index还可以保障角标不会越界。然后每次循环后index = next(index),next源码如下:

1 private int next(int index) {
2             return (index + 2) & mask;
3 }

看这里就会明白,index索引每次循环都会加2,说明不是挨个循环table数组中元素,而是0,2,4,6.。。。这种跳跃式循环,为什么这样呢?别急,分析完整个方法你会明白(PS:总告诉我别急,我都快急死了)

第9行:从table数组中取出对应索引的元素

第11-15行:取出的元素key与要存储的key的reference比较,如果相等则在下一个位置index+1,存储value值,然后return整个方法执行完毕。reference又是什么鬼?其实就是当前ThreadLocal的弱引用:

1  private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this);

也是为了内存考虑的。

11-15行主要判断要存储的数据之前是否存储过,如果存储过则覆盖存储。

17-32行:17行表明之前没有存储过要存储的数据,则进入判断。

18-24行:首先判断firstTombstone是否为-1,firstTombstone默认值-1,上面说了firstTombstone 用于记录已经删除的table数组中数据角标,如果等于-1,则表明没找到已经删除数据的索引。

20-23行:将要存储的数据依次存储在index和index+1的位置上。然后return整个方法。

27-31行:经过上面的分析这里就很好理解了,就是找到有删除的元素,并且firstTombstone就是被删除元素的key的索引,则将要存储数据的key.reference以及value依次存储在firstTombstone与firstTombstone+1位置上,也就是覆盖存储,覆盖已经被删除数据的位置。

35-37行:就是寻找被删除数据角标的过程,其中TOMBSTONE用于记录被删除的数据。

好了,到此整个存储过程就分析完了,是不是还有点蒙蔽,到底怎么存储的呢?上图(是时候展示我强大的画图能力了):

看到了吧,其实存储就是在数组相邻位置依次存储key.reference与value。看到这里上面的所有疑问就都解开了,为什么循环遍历的时候index是每次加2的?因为key的存储是隔一个元素存储的,key.reference与value才是一个数据整体。好了具体存储想说的就都说完了。

我们回头想一个问题?ThreadLocal是怎么将数据只存储在当前线程并且只有当前线程可以获取?其实只要仔细想想文章一开始就提到的ThreadLocal中set方法就不难理解了。

ThreadLocal中set方法一开始就获取当前线程,然后从当前线程获取Values对象,如果当前线程没有则给当前线程初始化一个Values对象,最终调用Values对象的put方法存储数据,获取数据也是同样的逻辑。重点就是无论存储还是获取数据都是调用当前线程中的Values对象的存储或者获取数据的方法,重点是当前线程的Values对象,这个对象只存储在当前线程,其余线程也存在Values对象,但是不同线程是不一样的,不同线程维护不同的Values对象,这就是核心所在。

至于ThreadLocal的get方法我就不继续分析了,最核心的部分上面已经分析完了,感兴趣的可以自己试着去分析一下。

为什么存储的key是当前ThreadLocal的弱引用?

试想一下如果我们存储的是ThreadLocal的强引用,我们将ThreadLocal对象置为null,此时ThreadLocal对应的内存会被顺利回收吗?显然不会,虽然ThreadLocal对象被置为null了,但是Values对象的数组中依然存储着ThreadLocal的强引用,对应内存不能顺利被回收,这就必然造成内存泄漏。

如果存储的是ThreadLocal的弱引用,那么Values对象table数组中对应key则可以被顺利收回,key对应的value在下一次调用的时候会被清除。

这里是不是又会出现一个问题?

由于Values和对应Thread生命周期是一样的,如果我们将ThreadLocal对象置为null,那么table中对应的key就可能被GC回收,当时此时value值还没有回收啊,是不是很别扭,这就要求我们写代码的时候要规范,每次使用完ThreadLocal都调用remove()方法,清除数据。

如下:

 1 ThreadLocal<String> t1 = new ThreadLocal<String>() {
 2 
 3             @Override
 4             protected String initialValue() {
 5                 //
 6                 return "adc";
 7             }
 8 };
 9         
10 Toast.makeText(MainActivity.this, t1.get(), 0).show();
11         
12 t1.remove();
13 t1 = null;

好了,本文到此结束,希望对你有帮助,废话就不多说了,学到的才是自己的。

下一篇分析Message.obtain()所说的消息池到底是什么玩意?怎么实现的?你真正的理解吗?

posted @ 2017-11-16 17:56  WangLei_ClearHeart  阅读(1210)  评论(0编辑  收藏  举报