Android源码之ThreaLocal

ThreadLocal提供了一种线程操作数据的思想。以前我以为ThreadLocal的实现就是有一个以线程的name为关键字的HashMap,每次请求数据的时候在这个HashMap中查找数据。今天读完代码,我才明白原来是用一种更巧妙的方法实现的。

下面我们就分析一下ThreadLocal的源码实现,

首先我们先看一下ThreadLocal的相关成员变量以及构造函数:

/**Weakreferencetothisthreadlocalinstance.*/

privatefinalReference<ThreadLocal<T>>reference=newWeakReference<ThreadLocal<T>>(this);

在这里可以将reference理解为关键字,下面我们可以看到reference的用处

privatestaticAtomicIntegerhashCounter=newAtomicInteger(0);

privatefinalinthash=hashCounter.getAndAdd(0x61c88647*2);

hash用于计算hash值,hash值就是为了计算索引地址

构造函数如下:

publicThreadLocal(){}

ThreadLocal的构造函数时一个空函数。

在ThreadLocal类中有一个static的内部类Values.Values类提供了存储和索引具体的值的逻辑

我们也来看一下Values的成员变量以及构造函数:

privatestaticfinalintINITIAL_SIZE=16;

初始的存放数据的数组的大小

privatestaticfinalObjectTOMBSTONE=newObject();

TOMBSTONE是一个占位符,表示一个墓碑

privateObject[]table;

保存数据的数组

privateintmask;

用于计算索引值的掩码

privateintsize;

size表示有效的数据的个数

privateinttombstones;

tombstones译为墓碑,表示索引值的数据无效,但是曾经被使用过的数据的个数

privateintmaximumLoad;

maximumLoad表示最大的装载数,也就是size+tombstones的最大值

privateintclean;

clean记录了下次执行cleanup操作的起始位置

Values只提供了默认构造函数,实现如下:

Values(){

initializeTable(INITIAL_SIZE);

this.size=0;

this.tombstones=0;

}

privatevoidinitializeTable(intcapacity){

this.table=newObject[capacity*2];

this.mask=table.length-1;

this.clean=0;

this.maximumLoad=capacity*2/3;//2/3

}

构造函数只是提供了成员变量的初始化。在实现中我们看到,将mask掩码初始化为table的长度-1,这样在计算索引值时,保证索引值落在table里面。将maximumLoad初始化为table的长度的2/3.

(1)set函数

接下来我们看一下ThreadLocal的set函数的实现,set函数将值设置到线程中去

publicvoidset(Tvalue){

//得到当前的运行线程

ThreadcurrentThread=Thread.currentThread();

//获得运行线程的Values值

Valuesvalues=values(currentThread);

//如果values值为空,则说明还没有为该线程设置过值,这时需要初始化一个空的values对象

if(values==null){

values=initializeValues(currentThread);

}

//设置值

values.put(this,value);

}

values函数的实现如下:

Valuesvalues(Threadcurrent){

returncurrent.localValues;

}

我们可以看到,values函数就是返回了线程的localValues成员变量。

在Thread类中,存在ThreadLocal.ValueslocalValues的成员变量,有着包访问权限。从这里我们可以看出,ThreadLocal的值是绑定到各个线程中去的,而不是在ThreadLocal里统一管理。

initializeValues函数的实现也很简单,如下:

ValuesinitializeValues(Threadcurrent){

returncurrent.localValues=newValues();

}

只是简单的初始化Thread的localValues成员变量new一个新Values对象。

接下来我们分析一下Values类的put函数的实现:

voidput(ThreadLocal<?>key,Objectvalue){

//一上来就执行cleanUp操作

cleanUp();

//记录第一个墓碑的位置

intfirstTombstone=-1;

//遍历表

for(intindex=key.hash&mask;;index=next(index)){

//得到关键字

Objectk=table[index];

//查找到关键字,则替换值

if(k==key.reference){

//Replaceexistingentry.

table[index+1]=value;

return;

}

//如果当前位置的关键字是空,说明查找到一个没有使用过的位置,这时候就必须将该值保存

if(k==null){

//firstTombstone==-1,说明在查找过程中没有遇到墓碑,则将该值保存在这个null位置

if(firstTombstone==-1){

//Fillinnullslot.

//保存key和value

table[index]=key.reference;

table[index+1]=value;

//有效的数据个数加1

size++;

return;

}

//firstTombstone!=-1,说明查找过程中遇到过墓碑,则要将该值替换遇到的第一个墓碑

//保存key和value

table[firstTombstone]=key.reference;

table[firstTombstone+1]=value;

//墓碑的个数减1

tombstones--;

//有效的个数加1

size++;

return;

}

//保存第一个墓碑的位置

if(firstTombstone==-1&&k==TOMBSTONE){

firstTombstone=index;

}

}

}

从put的实现逻辑中,我们看到Values是想一个位置保存key,在这个位置的下一个位置处保存value。这就要求table的长度必须为2的整数倍,并且计算索引的步长为2。

我们从next函数的视线中验证了这一点:

privateintnext(intindex){

return(index+2)&mask;

}

我们接下来看一下cleanUp函数的实现逻辑:

privatevoidcleanUp(){

if(rehash()){

//如果重新hash了table,则没有必要在进行cleanUp操作

return;

}

//size=0,表示还没有保存有效的数据

if(size==0){

//Noliveentries==nothingtoclean.

return;

}

//从上次执行cleanUp结束时的索引值开始

intindex=clean;

Object[]table=this.table;

//遍历table

for(intcounter=table.length;counter>0;counter>>=1,index=next(index)){

//得到key

Objectk=table[index];

if(k==TOMBSTONE||k==null){

continue;//ontonextentry

}

@SuppressWarnings("unchecked")

Reference<ThreadLocal<?>>reference=(Reference<ThreadLocal<?>>)k;

//如果为null,表示此对象已经被回收

if(reference.get()==null){

//将该位置设置成墓碑,并将值设置为null

table[index]=TOMBSTONE;

table[index+1]=null;

//增加墓碑的数目和减少有效的对象数目

tombstones++;

size--;

}

}

//保存下次cleanUp的其实索引值

clean=index;

}

从源码中可以看出,cleanUp主要清理了被垃圾回收器回收的对象。这样能保证每次取数据和设置数据时索引位置的有效性。

我们看一下rehash的函数实现:

privatebooleanrehash(){

//如果没有达到可装载的最大值,则不用重新hash,直接返回false

if(tombstones+size<maximumLoad){

returnfalse;

}

intcapacity=table.length>>1;

intnewCapacity=capacity;

//如果一半的容量都被有效的值所占据,则将容量扩大一倍。因为table是将关键字和值保存在相邻的位置上,所以这里其实比较的是table数组的长度的1/4

if(size>(capacity>>1)){

newCapacity=capacity*2;

}

Object[]oldTable=this.table;

//重新分配一个新的table数组

initializeTable(newCapacity);

this.tombstones=0;

//如果有效的值的个数为0,则没必要进行新旧table的赋值

if(size==0){

returntrue;

}

//进行新旧table的赋值

for(inti=oldTable.length-2;i>=0;i-=2){

Objectk=oldTable[i];

if(k==null||k==TOMBSTONE){

//Skipthisentry.

continue;

}

//Thetablecanonlycontainnull,tombstonesandreferences.

@SuppressWarnings("unchecked")

Reference<ThreadLocal<?>>reference=(Reference<ThreadLocal<?>>)k;

ThreadLocal<?>key=reference.get();

if(key!=null){

//该值是有效的,则加入到新的table中

add(key,oldTable[i+1]);

}else{

//该关键字对象已经被回收

size--;

}

}

returntrue;

}

add函数是将值赋值到table数组中。在遍历table的过程中,忽略掉当前索引位置的关键字是墓碑的情况,保证插入到null的位置

voidadd(ThreadLocal<?>key,Objectvalue){

for(intindex=key.hash&mask;;index=next(index)){

Objectk=table[index];

if(k==null){

table[index]=key.reference;

table[index+1]=value;

return;

}

}

}

(2)get()函数

get()函数负责取出已经保存的值。实现如下:

publicTget(){

//得到当前线程的Values对象

ThreadcurrentThread=Thread.currentThread();

Valuesvalues=values(currentThread);

if(values!=null){

Object[]table=values.table;

//计算索引值

intindex=hash&values.mask;

//如果还没有被垃圾回收

if(this.reference==table[index]){

return(T)table[index+1];

}

}else{

//为当前线程初始化values成员

values=initializeValues(currentThread);

}

//在table里没有查找到

return(T)values.getAfterMiss(this);

}

我们看一下getAfterMiss的实现:

ObjectgetAfterMiss(ThreadLocal<?>key){

//保存当前的数据table

Object[]table=this.table;

//计算索引值

intindex=key.hash&mask;

//如果当前关键字为null,则说明没有保存过该值,则没有继续查找下去的必要

if(table[index]==null){

//初始化一个值

Objectvalue=key.initialValue();

//如果在initialValue过程中,没有改变table

if(this.table==table&&table[index]==null){

//将关键字和值保存

table[index]=key.reference;

table[index+1]=value;

size++;

//执行一次cleanUp操作

cleanUp();

returnvalue;

}

//在initialValue的过程中,改变了table

//put到table中去

put(key,value);

returnvalue;

}

intfirstTombstone=-1;

//继续查找

for(index=next(index);;index=next(index)){

Objectreference=table[index];

//如果关键字相等

if(reference==key.reference){

returntable[index+1];

}

//如果当前索引的关键字为null,则表明当前位置还没有被使用过,则没有必要在查找下去

if(reference==null){

//初始化一个值

Objectvalue=key.initialValue();

//如果在initialValue过程中,没有改变table

if(this.table==table){

//如果我们曾经遇到过墓碑,并且现在那个位置还是个墓碑,则将值赋值到第一个墓碑的位置

if(firstTombstone>-1&&table[firstTombstone]==TOMBSTONE){

table[firstTombstone]=key.reference;

table[firstTombstone+1]=value;

tombstones--;

size++;

//Noneedtocleanuphere.Wearen'tfilling

//inanullslot.

returnvalue;

}

//当前索引的位置还是null,则赋值到当前的位置

if(table[index]==null){

table[index]=key.reference;

table[index+1]=value;

size++;

cleanUp();

returnvalue;

}

}

//在initialValue的过程中,改变了table

put(key,value);

returnvalue;

}

//记录第一个墓碑的位置

if(firstTombstone==-1&&reference==TOMBSTONE){

firstTombstone=index;

}

}

}

在ThreadLocal的实现中,initialValue只是简单的返回一个null,等待子类去实现。

protectedTinitialValue(){

returnnull;

}

至此,我们分析了set和get函数的实现。

ThreadLocal还实现了remove的逻辑,我们接下来来分析一下remove的实现

(3)remove()函数

remove函数的源码如下:

publicvoidremove(){

ThreadcurrentThread=Thread.currentThread();

Valuesvalues=values(currentThread);

if(values!=null){

values.remove(this);

}

}

得到当前线程对象的values成员之后,调用Values的remove函数即可。Values的remove函数实现如下:

voidremove(ThreadLocal<?>key){

cleanUp();

for(intindex=key.hash&mask;;index=next(index)){

Objectreference=table[index];

if(reference==key.reference){

//如果关键字相等,则将该位置标记为墓碑。

table[index]=TOMBSTONE;

table[index+1]=null;

tombstones++;

size--;

return;

}

if(reference==null){

//Noentryfound.

return;

}

}

}

至此,我们完整的分析了ThreadLocal的实现。我们思考一下问题:

为什么要使用一个叫墓碑的占位符呢?

我们知道在table数组中,每一个位置可以存放的对象要么等于null,要么是一个要保存的值。我们只要在按顺序遍历table,总会找到一个位置可以保存数据。如果要删除一个数据,只要将该位置设置为null。这样当查找到一个位置为null的时候,就可以知道这个位置肯定是没有被使用的位置,因此也就没有必要再查找下去了。那为什么还有多引入一个墓碑的对象呢?看似这个墓碑是多余的,其实不然!因为计算每个线程的索引值的时候,每个线程的索引值其实是固定的,每次都会映射到相同的位置。这样有了墓碑这个占位符对象之后,能保证别的对象不会使用这个位置,相当于将这个位置保护起来,以便留给特定的线程去使用。

从ThreadLocal的实现中,我们总结到,通过ThreadLocal.set()到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

相关推荐