Android中ThreadLocal的工作原理

来源:互联网 发布:淘宝皮草外套新款 编辑:程序博客网 时间:2024/05/17 18:14

一,写在前面

       Android中ThreadLocal有一个典型应用场景,存取主线程线程中的Looper对象,例如:在主线程中调用Looper.prepare(),与在子线程中调用Looper.prepare()初始化是不同线程的Looper对象。
       那么,ThreadLocal是什么呢?ThreadLocal通常称为“线程局部变量”,也就说某些数据是以线程为作用域,在不同线程中有不同的数据副本。说到作用域,作为对比,方法里的局部变量作用域是方法体,其他方法无法访问。简单来说,希望在指定线程中存储数据,并在取出指定线程中数据,但其他线程不可访问该数据。

       ThreadLocal是Java提供的原生APi,并不是Android特有的。同时Android5.0对ThreadLocal进行一些优化设计,与原生还是有区别的。本篇文章是为下篇分析Handler机制的工作流程做准备,但不准备涉及Looper相关的讲解,只对ThreadLocal在Android中表现进行分析。

二,ThreadLocal的使用示例

       直接上代码,如下:
public class MainActivity extends Activity {private ThreadLocal<String> mThreadLocal;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mThreadLocal = new ThreadLocal<String>();mThreadLocal.set("ActivityThread ... ");new Thread(){public void run() {//mThreadLocal.set("new Thread() ... ");Log.e("wcc", "子线程 : " + mThreadLocal.get());};}.start();Log.e("wcc", "主线程 : " + mThreadLocal.get());}}
       第9行,创建ThreadLocal对象,存储数据为String类型,各个线程可以共享该ThreadLocal对象;
       第10行,主线程中设置数据为"ActivityThread ... " ;
       第14行,注释了,子线程不设置数据;
       第15行,在子线程中获取该数据;
       第19行,在主线程中获取该数据;

      打印结果如下:
     12-05 07:57:38.981: E/wcc(822): 主线程 : ActivityThread ... 
     12-05 07:57:39.001: E/wcc(822): 子线程 : null

       从结果来看,在不同线程中调用ThreadLocal$get方法获取的值并不同。由于子线程没有调用ThreadLocal$set方法,取出的值是null,这是为什么呢,在后面的分析中会给出答案。

三,ThreadLocal的工作原理

       下面以Android5.0的ThreadLocal的源码来分析,首先看ThreadLocal的构造函数,源码如下:
    /**     * Creates a new thread-local variable.     */    public ThreadLocal() {}
        它是一个空实现,什么也没做。

       ThreadLocal存储数据,会调用set方法,查看ThreadLocal$set方法源码:
    public void set(T value) {        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values == null) {            values = initializeValues(currentThread);        }        values.put(this, value);    }    //继续查看...    Values values(Thread current) {        return current.localValues;    }    //继续查看        Values initializeValues(Thread current) {        return current.localValues = new Values();    }
       第2行,获取当前线程对象,在上述例子中指的是ActivityThread,也就是主线程;
       第3行与第12行结合看,返回Thread类的localValues字段;该字段在Thread类中定义:ThreadLocal.Values localValues;
       第5行,如果values为null,即localValues为null,则调用initializeValues方法;
       第18行,initializeValues方法里面创建了一个Values对象,并初始化字段localValues。Values类是ThreadLocal的一个内部类,在java原生的ThreadLocal中,代替Values类的是ThreadLocalMap类。由上面可知,每一个Thread都会先初始化localValues字段,也即创建一个该线程的Values对象,每个线程的Values对象都是不同的,于是ThreadLocal可以在不同的线程中互不干扰的存储,查询数据
       第7行,调用内部类Values$put方法,存储数据。

       查看ThreadLocal$Values$put方法源码:
void put(ThreadLocal<?> key, Object value) {    cleanUp();    // Keep track of first tombstone. That's where we want to go back    // and add an entry if necessary.    int firstTombstone = -1;    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) {    if (firstTombstone == -1) {// Fill in null slot.table[index] = key.reference;table[index + 1] = value;size++;return;    }    // Go back and replace first tombstone.    table[firstTombstone] = key.reference;    table[firstTombstone + 1] = value;    tombstones--;    size++;    return;}// Remember first tombstone.if (firstTombstone == -1 && k == TOMBSTONE) {    firstTombstone = index;}    }}
       第9行,table是一个Object对象数组,定义:private Object[] table;
       第11行,reference是ThreadLocal的一个字段,定义:private final Reference<ThreadLocal<T>> reference = new WeakReference<ThreadLocal<T>>(this),用弱引用对ThreadLocal进行包装。
       第13,21,28行,将ThreadLocal的弱引用和需要存储的数据value放在table数组的相邻位置,形成一种映射关系,reference的索引位置加1就是value的索引位置。
       从上面的分析可知,在当前的线程的Values对象中,维护了一个Object对象数组,并将ThreadLocal的弱引用与需要存储的数据,存放在数组的相邻位置。

       ThreadLocal查询数据,会调用get方法,查看ThreadLocal$get方法源码:
    public T get() {        // Optimized for the fast path.        Thread currentThread = Thread.currentThread();        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 = initializeValues(currentThread);        }        return (T) values.getAfterMiss(this);    }
       第4行,获取当前线程的Values对象;
       第8行,若ThreadLocal的弱引用在table数组中的索引位置是index,继续往下执行;
       第9行,table数组索引位置为index + 1中存储了需要查询的数据,return该数据;
       第12行,创建当前线程的Values对象,并赋值给Thread的字段localValues;
       第15行,若当前线程的Values对象为空时,也就是未调用set方法存储值时,调用Value$getAfterMiss方法并return,下面会继续分析这里。

       查看ThreadLocal$Value$getAfterMiss方法源码:
    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;            }    //...code       }    //继续查看...    protected T initialValue() {        return null;    }
       第7行,调用ThreadLocal$initialValue方法,并赋值给变量value;
       第21行,return value;
       第30行,initialValue方法修饰符是protected,也就是它希望ThreadLocal子类来重写这个方法。
       第31行,initialValue方法返回null;
       
       也就是说,如果当前线程中没有调用ThreadLocal$set方法存储数据时,调用ThreadLocal$get方法查询数据会返回null。这也解释前面打印的log中,子线程为什么是null。当然,可以在创建ThreadLocal子类实例时,重写initialValue方法。
        将上述示例中创建ThreadLocal实例的代码,修改如下:
    mThreadLocal = new ThreadLocal<String>(){@Overrideprotected String initialValue() {return "initialValue ... ";}    };
       运行,打印log如下:
      12-05 09:32:49.111: E/wcc(7850): 主线程 : ActivityThread ... 
      12-05 09:32:49.131: E/wcc(7850): 子线程 : initialValue ... 

四,最后   

        本篇文章先是通过一个简单的示例展示ThreadLocal的使用,可以初步了解ThreadLocal想要完成是什么样的效果。后面通过ThreadLocal的set,get方法来阐述ThreadLocal的工作原理。简单来说,ThreadLocal可以在不同线程(作用域)中,线程间互不干扰的存储和查询数据。
       Android中Looper在不同线程中表现为不同的Looper对象,同时创建Handler对象时,会检查该线程中是否创建了Looper对象。那么,如何确定某一线程中是否创建Looper呢,使用ThreadLocal的特性可以很方便实现当前线程中Looper的存取操作。
       
       值得一提的是,ThreadLocal声明的泛型是T类型,相当于Object类型。本示例中存取的String类型数据,若同时想存取其他类型的数据,需要创建一个新的ThreadLocal对象。一个ThreadLocal对象,只能存取一种类型的数据,并在不同线程中有不同的数据副本。

     

       

    

            

       

        




原创粉丝点击