Android应用开发中ThreadLocal的工作原理
小标 2018-05-23 来源 : 阅读 1251 评论 0

摘要:在Android应用开发学习中,Android中ThreadLocal有一个典型应用场景,存取主线程线程中的Looper对象,例如:在主线程中调用Looper.prepare(),与在子线程中调用Looper.prepare()初始化是不同线程的Looper对象。希望对大家学习Android应用开发有所帮助。

一,写在前面

在Android应用开发学习中,Android中ThreadLocal有一个典型应用场景,存取主线程线程中的Looper对象,例如:在主线程中调用Looper.prepare(),与在子线程中调用Looper.prepare()初始化是不同线程的Looper对象。希望对大家学习Android应用开发有所帮助。

那么,ThreadLocal是什么呢?ThreadLocal通常称为“线程局部变量”,也就说某些数据是以线程为作用域,在不同线程中有不同的数据副本。说到作用域,作为对比,方法里的局部变量作用域是方法体,其他方法无法访问。简单来说,希望在指定线程中存储数据,并在取出指定线程中数据,但其他线程不可访问该数据。

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

二,ThreadLocal的使用示例

直接上代码,如下:

public class MainActivity extends Activity {
    private ThreadLocal<string> mThreadLocal;
 
    @Override
    protected 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());
    }
}</string></string>

   

第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> reference = new WeakReference<threadlocal>(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>(){
@Override
protected String initialValue() {
    return "initialValue ... ";
}
};</string>

   

运行,打印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对象,只能存取一种类型的数据,并在不同线程中有不同的数据副本。

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发Android频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved