彻底理解Android中的ThreadLocal
一、概念
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中才可以访问,其他线程则无法获取。
二、使用场景
1、当某些数据是以线程为作用域且不同线程之间具有不同数据副本的时候,就可以考虑使用ThreadLocal,例如Android中的Handler消息机制,Looper的作用域就是线程且不同线程之间具有不同的Looper,这里就使用的是ThreadLocal对Looper与线程进行关联,如果不使用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler
查找指定的线程的Looper,这肯定比ThreadLocal复杂多了。
2、当复杂逻辑下进行对象传递时,也可以考虑使用ThreadLocal,例如一个线程中执行的任务比较复杂,我们需要一个监听器去贯穿整个任务的执行过程,如果不使用ThreadLocal,那么我们就需要将一个监听器从函数调用栈层层传递,这对程序设计来说是不可接受的,而使用ThreadLocal我们就可以在需要的时候get获取,方便至极。
三、使用
来看一个简单的例子
public class MainActivity extends Activity { private ThreadLocal mThreadLocal = new ThreadLocal(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //主线程 mThreadLocal.set(true); new Thread() { @Override public void run() { super.run(); //子线程 mThreadLocal.set(false); Log.i("ThreadLocal子线程",String.valueOf(mThreadLocal.get())); } }.start(); //保证子线程先去更新值 try { getMainLooper().getThread().sleep(3000); Log.i("ThreadLocal主线程",String.valueOf(mThreadLocal.get())); } catch (InterruptedException e) { e.printStackTrace(); } }}
执行结果:
07-05 10:57:02.995 15966-15992/android_p.lbjfan.cm.memoryanalysedemo I/ThreadLocal子线程: false07-05 10:57:05.995 15966-15966/android_p.lbjfan.cm.memoryanalysedemo I/ThreadLocal主线程: true
结论:ThreadLocal在当前线程操作数据只对当前线程有效
三、源码分析
1、构造方法
public ThreadLocal() {}
2、set方法
public void set(T value) { //获取当前线程 Thread currentThread = Thread.currentThread(); //获取当前线程的Values对象 Values values = values(currentThread); //如果是空就新建 if (values == null) { values = initializeValues(currentThread); } //使用values的put方法存值 values.put(this, value);}
3、get方法
public T get() { //获取当前线程 Thread currentThread = Thread.currentThread(); //获取对应的values对象 Values values = values(currentThread); if (values != null) { Object[] table = values.table; //获取索引 int index = hash & values.mask; //获取值 if (this.reference == table[index]) { return (T) table[index + 1]; } } else { //初始化Values对象 values = initializeValues(currentThread); } //通过values的getAfterMiss方法获取值 return (T) values.getAfterMiss(this);}
4、Values基础定义
Values是ThreadLocal的静态内部类,首先来看一些基础的定义
static class Values { //数组初始值大小,必须是2的N次方 private static final int INITIAL_SIZE = 16; //被删除的数据 private static final Object TOMBSTONE = new Object(); //存放数据的数组,使用key/value映射大小总是2的N次方 private Object[] table; //和key的hash值进行与运算,获取数组中的索引 private int mask; //当前有效的Key的数量 private int size; //已经失效的key的数量 private int tombstones; //key的总和 private int maximumLoad; //下一次查找失效key的起始位置 private int clean;
由上面一些基础的定义我们知道,Values作为ThreadLocal的静态内部类,是真正意义上对数据进行存储、更新、及删除的类,内部使用数组对数据进行存储,存储结构为:key,value,key,value......
5、Values的构造函数
//普通构造函数 Values() { initializeTable(INITIAL_SIZE); this.size = 0; this.tombstones = 0; } //使用外部Values拷贝的构造函数 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); } //初始化数组大小 private void initializeTable(int capacity) { //存储数据的table数组大小默认为32 this.table = new Object[capacity * 2]; //mask的默认大小为table的长度减1,即table数组中最后一个元素的索引 this.mask = table.length - 1; this.clean = 0; //默认存储的最大值为数组长度的1/3 this.maximumLoad = capacity * 2 / 3; // 2/3 }
6、Values的put方法
void put(ThreadLocal<?> key, Object value) //这个后面在看 cleanUp(); //标记第一个失效数据的索引 int firstTombstone = -1; //使用key的hash和mask进行&运算,获取当前key在数组中的index for (int index = key.hash & mask;; index = next(index)) { //直接获取key Object k = table[index]; //如果key相同,则直接更新value,key对应的索引为index,则value的索引为index+1 if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } //如果key不存在 if (k == null) { //当前不存在失效key if (firstTombstone == -1) { //将key和value保存到数组 table[index] = key.reference; table[index + 1] = value; size++; return; } //如果存在失效的key,则将需要存储的值保存到失效的key所在的位置 table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } //对失效的key进行标记 if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }
可以看到,put方法其实很简单,使用key的hash和table数组的长度减1进行&运算,获取index,然后有则更新,无则添加,添加时则利用key的有效性及失效的key尽可能的节省空间。
7、clearUp方法:将失效的key进行标记,释放它的值
private void cleanUp() { //如果需要扩容,则直接返回,扩容的过程中对失效的地方进行了标记 if (rehash()) { return; } //没有值的话,什么也不做 if (size == 0) { return; } //默认是0,标记每次clean的位置 int index = clean; Object[] table = this.table; for (int counter = table.length; counter > 0; counter >>= 1, index = next(index)) { Object k = table[index]; //如果key是失效的或者为null,则不做处理 if (k == TOMBSTONE || k == null) { continue; // on to next entry } //table只能存储null、tombstone、和references @SuppressWarnings("unchecked") Reference> reference = (Reference>) k; //检查key是否失效,失效的话进行标记并且释放它的值 if (reference.get() == null) { // This thread local was reclaimed by the garbage collector. table[index] = TOMBSTONE; table[index + 1] = null; tombstones++; size--; } } //标记下次开始clean的位置 clean = index;}
8、rehash方法:对数组进行扩容
private boolean rehash() { //不需要扩容 if (tombstones + size < maximumLoad) { return false; } int capacity = table.length >> 1; int newCapacity = capacity; if (size > (capacity >> 1)) { //双倍扩容 newCapacity = capacity * 2; } //标记数组 Object[] oldTable = this.table; //重新初始化数组大小 initializeTable(newCapacity); // 重置失效的key this.tombstones = 0; //没有有效的key if (size == 0) { return true; } //数组扩容 for (int i = oldTable.length - 2; i >= 0; i -= 2) { Object k = oldTable[i]; //丢弃失效的key if (k == null || k == TOMBSTONE) { continue; } @SuppressWarnings("unchecked") Reference> reference = (Reference>) k; ThreadLocal<?> key = reference.get(); if (key != null) { 添加有效的key和value add(key, oldTable[i + 1]); } else { // The key was reclaimed. size--; } } return true; } add方法:table数组内部是如何进行数据存储的 void add(ThreadLocal<?> key, Object value) { //根据key的hash和mask进行&运算,获取index for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == null) { //将key存储到数组的index位置 table[index] = key.reference; //将value存储到数组的index+1位置 table[index + 1] = value; return; } } }
小结:ThreadLocal的set方法内部调用其静态内部类Values的put方法,内部采用table数组对数据进行存储,使用table[index]=key,table[index+1]=value的方式,在存储的过程中会进行数组扩容、失效key的标记,值的释放等操作。
9、getAfterMiss方法:之前没有set过值,调用get方法时会调用到此方法
Object getAfterMiss(ThreadLocal<?> key) { Object[] table = this.table; //获取索引 int index = key.hash & mask; //如果key不存在,那么直接返回ThreadLocal方法返回值,默认为null if (table[index] == null) { Object value = key.initialValue(); //如果是同一个数组且key不存在,(疑问为什么回不相等,方法开始赋的值) if (this.table == table && table[index] == null) { table[index] = key.reference; table[index + 1] = value; size++; cleanUp(); return value; } // 添加到table数组 put(key, value); return value; } //key存在时的处理 int firstTombstone = -1; // Continue search. for (index = next(index);; index = next(index)) { Object reference = table[index]; //根据index查到且相等直接返回 if (reference == key.reference) { return table[index + 1]; } //如果没有查到,继续返回默认的value 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; } //标记无效的key if (firstTombstone == -1 && reference == TOMBSTONE) { // Keep track of this tombstone so we can overwrite it. firstTombstone = index; } } }
经过上面的分析,ThreadLocal之所以能够在不同的线程中存储数据副本,是因为每个Thread都有一个Values对象,该对象中的table数组进行真正的存储。当我们使用ThreadLocal
的get或者set方法时,会更据当前线程获取到该线程内部的Values对象,然后获取内部的values数组,最后在进行数据的存储或删除。
四、Android Handler消息机制中的ThreadLocal
我们都知道,当我们在一个线程中使用Looper的时候,需要调用Looper的prepare方法和loop方法,不然就会出现异常。那么,我们就看看这两个方法的源码:
public static void prepare() { prepare(true);}private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}loop方法内部先调用了myLooper方法:public static @Nullable Looper myLooper() { return sThreadLocal.get();}
很明显,Looper 的prepare方法先创建Looper,并使用ThreadLocal存储即与当前的线程进行关联,然后loop方法开启消息机制的时候,使用ThreadLocal方法获取到当前线程的Looper
方法。
结语:ThreadLocal是一种针对线程间数据副本不同的巧妙设计,开发者无需理会内部的复杂实现,只需在调用的时候使用get和set方法即可!对外屏蔽了细节,是一种设计思想的体现,其内在table数组内存的利用和空间的扩展也值得我们学习。
更多相关文章
- Android任务、进程、线程的关系
- Android 主线程到底是什么、如何抛出ANR
- 调试方法-Unity3D对各个target平台的模拟
- Android 的线程跟线程池
- Android读取工程内嵌资源文件的两种方法
- Android Studio:10分钟教会你做百度地图定位!并解决SDK22中方法报
- Android——Json和Gson分别是什么,以及Json 数据的解析方法
- Android 性能优化——通过线程提高性能