Android中ThreadLocal的工作原理

一,写在前面

       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;

	@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());
	}
}
       第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>(){
	@Override
	protected 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对象,只能存取一种类型的数据,并在不同线程中有不同的数据副本。

     

       

    

            

       

        




发布了47 篇原创文章 · 获赞 28 · 访问量 8万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览