Android开发之ThreadLocal原理深入理解
[Android]ThreadLocal的定义和用途
ThreadLocal用于实现在不同的线程中存储线程私有数据的类。在多线程的环境中,当多个线程需要对某个变量进行频繁操作,同时各个线程间不需要同步,此时,各个子线程只需要对存储在当前线程中的变量的拷贝进行操作即可,程序的运行效率会很高,即所谓的空间换时间。
在开源框架EventBus和android系统的Looper类当中有运用到ThreadLocal进行线程局部数据的存储,android版的ThreadLocal和java原生的ThreadLocal有一定的差别,android版的进行了一些优化设计,通过内部类Values中的Object数组来存储ThreadLocal的弱引用和线程的局部数据对象;而java版的是以MAP的方式来存储。
[Android]ThreadLocal的基本使用
ThreadLocal的使用很简单,定义一个全局的ThreadLocal对象,在线程中通过get()方法得到当前线程的局部数据进行操作。
ThreadLocal<ClassType> storageDataThreadLocal = new ThreadLocal<ClassType>(){
    @Override
    protected ClassType initialValue() {
        return new ClassType();
    }
};
ClassType为自定义的数据类,实例化ThreadLocal对象过程中如果没有重写initialValue方法则第一次调用get方法会返回空,这里需要注意。
[Android]ThreadLocal实现线程数据拷贝的原理
我们关注最多的应该就是ThreadLocal如何实现线程的局部数据存储,通过分析源码可以得到最终的答案。
我们直接分析get函数:
/**
 * Returns the value of this variable for the current thread. If an entry
 * doesn't yet exist for this variable on this thread, this method will
 * create an entry, populating the value with the result of
 * {@link #initialValue()}.
 *
 * @return the current value of the variable for the calling thread.
 */
@SuppressWarnings("unchecked")
public T get() {
    // Optimized for the fast path.
    //通过native方法得到代码当前执行的线程对象
    Thread currentThread = Thread.currentThread();
    //得到当前线程的Values类型对象
    Values values = values(currentThread);
    if (values != null) {
    	  //得到对象数组table,用来存储当前ThreadLocal对象的弱引用
    	  //和当前线程的局部数据对象引用
        Object[] table = values.table;
        //得到用于存储ThreadLocal弱引用的散列值索引
        int index = hash & values.mask;
        //指向到对象相同,则返回其引用
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
    	  //新建values对象赋给当前线程对象的localValues引用
        values = initializeValues(currentThread);
    }
    return (T) values.getAfterMiss(this);
}
如果当前线程对象内的Values对象中有存储和当前ThreadLocal对象的reference属性是同一个对象的引用则返回此线程的本地数据,即变量在线程的本地拷贝。
否则在当前线程内部创建一个Values对象,通过Values.getAfterMiss将ThreadLocal类的initialValue方法得到的本地数据存储到当前线程对象内部并返回;initialValue是protected方法,如果子类没有重写,则默认返回空。
静态内部类Values的getAfterMiss函数:
/**
* Gets value for given ThreadLocal after not finding it in the first
* slot.
*/
Object getAfterMiss(ThreadLocal<?> key) {
	Object[] table = this.table;
	//通过散列算法得到ThreadLocal的first slot的索引值
   int index = key.hash & mask;
   // If the first slot is empty, the search is over.
   if (table[index] == null) {
   	 //如果first slot上没有存储 则将ThreadLocal的弱引用和本地数据
   	 //存储到table数组的相邻位置并返回本地数据对象引用
       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().
         //遍历table数组,根据不同判断将ThreadLocal的
         //弱引用和本地数据对象引用存储到table数组的相应位置
         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;
         }
     }
 }
getAfterMiss函数根据不同的判断将ThreadLocal的弱引用和当前线程的本地对象以类似MAP的方式,存储在table数组的相邻位置,其中其散列的索引hash值是通过hashCounter.getAndAdd(0x61c88647 * 2)算法来得到。
set函数:
/**
 * Sets the value of this variable for the current thread. If set to
 * {@code null}, the value will be set to null and the underlying entry will
 * still be present.
 *
 * @param value the new value of the variable for the caller thread.
 */
public void set(T value) {
    Thread currentThread = Thread.currentThread();
    //得到当前线程的本地Values对象
    Values values = values(currentThread);
    if (values == null) {
    	  //为空则新建Values对象
        values = initializeValues(currentThread);
    }
    //将当前ThreadLocal对象引用和本地数据存储到
    //当前ThreadLocal的内部Values对象的table数组当中
    //当调用get方法时,获得的即是当前线程的本地数据
    values.put(this, value);
}
[Android]关于ThreadLocal内存泄漏的问题
在其他的博文中有说到,java版的ThreadLocal“value值因为存在一条从current thread连接过来的强引用,只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.”.
在android版的ThreadLocal中value值不存在强引用,故在table数组中的指定位置置为null,本地数据则没有存在指向其的引用,当GC回收的时候会清理掉。
[Android]ThreadLocal总结
ThreadLocal实现线程本地存储的原理是比较清晰的,即在当前线程中调用get方法时,通过ThreadLocal的initialValue方法创建当前线程的一个本地数据拷贝,将此拷贝添加到当前线程本地数据的table数组当中;或者在调用set方法时,将当前线程的本地数据存储到当前线程的table数组中.当前线程通过调用ThreadLocal对象的get方法即得到当前线程本地数据对象。
Android开发之ThreadLocal原理深入理解的更多相关文章
- Android开发之InstanceState详解
		Android开发之InstanceState详解 本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceS ... 
- 【Android UI】Android开发之View的几种布局方式及实践
		引言 通过前面两篇: Android 开发之旅:又见Hello World! Android 开发之旅:深入分析布局文件&又是“Hello World!” 我们对Android应用程序运行原理 ... 
- Android开发之InstanceState详解(转)---利用其保存Activity状态
		Android开发之InstanceState详解 本文介绍Android中关于Activity的两个神秘方法:onSaveInstanceState() 和 onRestoreInstanceS ... 
- Android开发之旅4:应用程序基础及组件
		引言 为了后面的例子做准备,本篇及接下来几篇将介绍Android应用程序的原理及术语,这些也是作为一个Android的开发人员必须要了解,且深刻理解的东西.本篇的主题如下: 1.应用程序基础 2.应用 ... 
- Android开发之旅3:android架构
		引言 通过前面两篇: Android 开发之旅:环境搭建及HelloWorld Android 开发之旅:HelloWorld项目的目录结构 我们对android有了个大致的了解,知道如何搭建andr ... 
- Android开发之Java必备基础
		Android开发之Java必备基础 Java类型系统 Java语言基础数据类型有两种:对象和基本类型(Primitives).Java通过强制使用静态类型来确保类型安全,要求每个变量在使用之前必须先 ... 
- Android开发之PopupWindow
		/* * Android开发之PopupWindow * * Created on: 2011-8-8 * Author: blueeagle * Email: liujiaxiang@g ... 
- Android 开发之旅:深入分析布局文件&又是“Hello World!”
		http://www.cnblogs.com/skynet/archive/2010/05/20/1740277.html 引言 上篇可以说是一个分水岭,它标志着我们从Android应用程序理论进入实 ... 
- Android开发之eclipse 快捷键
		转自:<Android开发之eclipse 快捷键>http://www.cnblogs.com/aimeng/archive/2012/08/07/2626909.html Ctrl+1 ... 
随机推荐
- 互斥的数(codevs 1553)
			题目描述 Description 有这样的一个集合,集合中的元素个数由给定的N决定,集合的元素为N个不同的正整数,一旦集合中的两个数x,y满足y = P*x,那么就认为x,y这两个数是互斥的,现在想知 ... 
- 银河英雄传说  2002年NOI全国竞赛
			时间限制: 1 s 空间限制: 256000 KB 题目等级 : 大师 Master 题目描述 Description 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年 ... 
- vim下多行注释与解注释
			1.多行注释 (1)按esc进入命令行模式 (2)按下Ctrl+v,进入区块模式,并使用上下键选择需要注释的多行 (3)按下“I”(大写)键,进入插入模式 (4)输入注释符(“//”或“#”等) (5 ... 
- codevs1127 接水问题
			题目描述 Description 学校里有一个水房,水房里一共装有m 个龙头可供同学们打开水,每个龙头每秒钟的供水量相等,均为1. 现在有n 名同学准备接水,他们的初始接水顺序已经确定.将这些同学按接 ... 
- Minimal string 栈 贪心
			time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ... 
- shell学习三十七天----引用
			引用 案例,假设我想输出一个星号(*),使用echo怎样做? echo * 这是肯定不行的,须要将*转移,即:echo \* 这样就引出了引用的概念.所为引用,是用来防止shell将某些你想要的东西解 ... 
- 深圳MPD大会 讲师演讲稿 2014-10
			 深圳MPD大会 讲师演讲稿 2014-10 互联网下的蛋-姜志辉.pdf: http://www.t00y.com/file/76704370 俞炜-互联网研发整形术 终于版.pdf: htt ... 
- 【bzoj1015】[JSOI2008]星球大战starwar
			给定一个无向图,求联通块个数,以及k次每次摧毁一个点后的联通块个数 将边和摧毁的点全记录下来,反着做即可 注意被摧毁的点不能算作联通块 #include<algorithm> #inclu ... 
- SQL 2005批量插入数据的二种方法
			SQL 2005批量插入数据的二种方法 Posted on 2010-07-22 18:13 moss_tan_jun 阅读(2635) 评论(2) 编辑 收藏 在SQL Server 中插入一条数据 ... 
- document.getElementById方法在火狐和谷歌浏览器兼容
			转自:http://www.office68.com/computer/6505.html 对于前台设计,浏览不兼容是一个很头晕的事情,为此记录下来与大家分享,并供日后自己参考. 例:有一个名为pwd ... 
