您的位置:首页 > 移动开发 > Android开发

android ThreadLocal 深入解析

2016-09-09 17:52 387 查看
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

ThreadLocal 使用场景:

1:当某些数据是以线程为作用域且不同线程具有不同的数据副本的时候,可以考虑采用ThreadLocal;

2:复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中任务过于复杂,这可能表现为函数调用栈比较深入以及代码入口的多样化,这种情况下,我们可以采用ThreadLocal,采用ThreadLocal 可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法来获取到监听器即可。

ThreadLocal 使用方式:

final ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>();
threadLocal.set(true);
Log.d("TAG","this thread name is"+ Thread.currentThread().getName()+"threadlocal get="+ threadLocal.get());

new Thread("#Thread1"){
@Override
public void run() {
threadLocal.set(false);
Log.d("TAG","this thread name is"+ Thread.currentThread().getName()+"threadlocal get="+ threadLocal.get());
}
}.start();

new Thread("#Thread2"){
@Override
public void run() {
Log.d("TAG","this thread name is"+ Thread.currentThread().getName()+"threadlocal get="+ threadLocal.get());
}
}.start();


运行结果:

09-08 23:21:32.800 32434-32434/com.example.pc.threadlocal D/TAG: this thread name ismainthreadlocal get=true
09-08 23:21:32.802 32434-353/com.example.pc.threadlocal D/TAG: this thread name is#Thread1threadlocal get=false
09-08 23:21:32.804 32434-354/com.example.pc.threadlocal D/TAG: this thread name is#Thread2threadlocal get=null


从上面的log日志我们可以看出,虽然在不同的线程中访问的是同一个ThreadLocal对象,但是通过ThreadLocal得到的值却是不同的。这就是ThreadLocal 的神奇之处。

对于ThreadLocal主要分为java jdk中的ThreadLocal,以及android sdk中ThreadLocal:

ThreadLocal 内部原理解析

在解析android sdk 内部ThreadLocal时需要先关注ThreadLocal内部类Values该内部类源代码是比较多的,其主要作用被设计用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量,而内部是如何实现该存储的,需要对源码进行进一步的分析。

下面针对该内部类中的各个方法进行细化分析。

ThreadLocal构造函数:

public ThreadLocal() {}


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);
}


代码中可以看到 set时,首先获取了当前线程,然后通过当前线程获取了一个Values对象,如果values为空,则 通过 initializeValues 该方法初始化一个Values对象.

Values initializeValues(Thread current) {
return current.localValues = new Values();
}


而对于Values可以看到实质上是ThreadLocal的一个内部类其官网解释:

Per-thread map of ThreadLocal instances to values.

针对该Values可以理解为:Values是被设计用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量。下面先对Values进行源码分析。

在分析Values内部类中的各个方法之前,先来介绍一下Values内部类中的各个成员变量:

/**
* Size must always be a power of 2.
*/
private static final int INITIAL_SIZE = 16;

/**
* Placeholder for deleted entries.
*/
private static final Object TOMBSTONE = new Object();

/**
* Map entries. Contains alternating keys (ThreadLocal) and values.
* The length is always a power of 2.
*/
private Object[] table;

/** Used to turn hashes into indices. */
private int mask;

/** Number of live entries. */
private int size;

/** Number of tombstones. */
private int tombstones;

/** Maximum number of live entries and tombstones. */
private int maximumLoad;

/** Points to the next cell to clean up. */
private int clean;


table:是实际上保存变量的地方,但它在这里是个Object类型的数组,它的长度必须是2的n次方的值。

mask:即计算下标的掩码,它的值是table的长度-1。

size:表示存放进来的实体的数量。这与前面原生的ThreadLocal的ThreadLocalMap是一样的。

tombstones:表示被删除的实体的数量

maximumLoad:是一个阈值,用来判断是否需要进行rehash

clean:表示下一个要进行清理的位置点。

/**
* Constructs a new, empty instance.
*/
Values() {
initializeTable(INITIAL_SIZE);
this.size = 0;
this.tombstones = 0;
}


/**
* Used for InheritableThreadLocals.
*/
Values(Values fromParent) {
this.table = fromParent.table.clone();
this.mask = fromParent.mask;
this.size = fromParent.size;
this.tombstones = fromParent.tombstones;
this.maximumLoad = fromParent.maximumLoad;
this.clean = fromParent.clean;
inheritValues(fromParent);
}


Values内部类存在两个构造函数分别为带参数与不带参数。下面来对这两个构造函数进行分析:

1:不带参数构造函数,不带参数构造函数中可以看到初始化了size以及tombstones,同时执行了initializeTable()方法,在该方法中

/**
* Creates a new, empty table with the given capacity.
*/
private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}


可以看到初始化了一个Object数组,数组长度为 private static final int INITIAL_SIZE = 16;

同时对 mask、clean、maximumLoad 这些int值进行初始化。

2:带参数的构造函数,可以看到带参数的构造函数中所带有的参数为Values,在构造函数内部可以看到主要是将通过参数传入的的Values赋值给构造函数构造出的Values。在该构造函数中可以着重看该方法:

/**
* Inherits values from a parent thread.
* 通过父线程继承values
*/
@SuppressWarnings({"unchecked"})
private void inheritValues(Values fromParent) {
// Transfer values from parent to child thread.
Object[] table = this.table;
for (int i = table.length - 2; i >= 0; i -= 2) {
Object k = table[i];

if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}

// The table can only contain null, tombstones and references.
Reference<InheritableThreadLocal<?>> reference
= (Reference<InheritableThreadLocal<?>>) k;
// Raw type enables us to pass in an Object below.
InheritableThreadLocal key = reference.get();
if (key != null) {
// Replace value with filtered value.
// We should just let exceptions bubble out and tank
// the thread creation
table[i + 1] = key.childValue(fromParent.table[i + 1]);
} else {
// The key was reclaimed.
table[i] = TOMBSTONE;
table[i + 1] = null;
fromParent.table[i] = TOMBSTONE;
fromParent.table[i + 1] = null;

tombstones++;
fromParent.tombstones++;

size--;
fromParent.size--;
}
}
}


可以看出内部遍历table数组,获取到对用的 Object 值,如果该position Object == null 或者 Object == TOMBSTONE 则跳过该次循环,继续下次循环。

当Object 值不为null并且 Object != TOMBSTONE则将该Object 强转为 Reference

Values values = values(currentThread);


Values values(Thread current) {
return current.localValues;
}


获取当前线程的Values对象,至于如何通过Thread.currentThread()当前线程获取到对应的values对象,可以通过Thread.java自行了解,本文主要讲解ThreadLocal。

if (values == null) {
values = initializeValues(currentThread);
}


在获取到当前线程values对象之后,首先判断当前线程values对象是否为null ,如果为null,

 Values initializeValues(Thread current) {
return current.localValues = new Values();
}


执行该方法重新new一个Values对象,同时赋值给 current.localValues变量。可以看到直接new Values(),通过Values的空的构造函数,得到一个长度为16的Object数组。

values如果不为null,或者values为null 且

if (values == null) {
values = initializeValues(currentThread);
}


已执行完上述代码,之后可以看到直接执行了

values.put(this, value);


而该段代码才是我们具体需要关注的,ThreadLocal 是如何实现线程内部的数据存储类。

/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
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;
}
}
}


上述便是put的具体源码,可以看到 key值为 ThreadLocal本身或者其子类,value 为Object类型,这就表示ThreadLocal是可以存储任意类型的数据。

在put代码中可以看到,首先执行了 cleanUp方法

/**
* Cleans up after garbage-collected thread locals.
*/
private void cleanUp() {
if (rehash()) {
// If we rehashed, we needn't clean up (clean up happens as
// a side effect).
return;
}

if (size == 0) {
// No live entries == nothing to clean.
return;
}

// Clean log(table.length) entries picking up where we left off
// last time.
int index = clean;
Object[] table = this.table;
for (int counter = table.length; counter > 0; counter >>= 1,
index = next(index)) {
Object k = table[index];

if (k == TOMBSTONE || k == null) {
continue; // on to next entry
}

// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
if (reference.get() == null) {
// This thread local was reclaimed by the garbage collector.
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
}
}

// Point cursor to next index.
clean = index;
}


可以看出cleanUp主要是对本地线程的一个垃圾清理。而在清理之前可以看到内部首先执行了一个rehash()方法

/**
* Rehashes the table, expanding or contracting it as necessary.
* Gets rid of tombstones. Returns true if a rehash occurred.
* We must rehash every time we fill a null slot; we depend on the
* presence of null slots to end searches (otherwise, we'll infinitely
* loop).
*/
private boolean rehash() {
if (tombstones + size < maximumLoad) {
return false;
}

int capacity = table.length >> 1;

// Default to the same capacity. This will create a table of the
// same size and move over the live entries, analogous to a
// garbage collection. This should only happen if you churn a
// bunch of thread local garbage (removing and reinserting
// the same thread locals over and over will overwrite tombstones
// and not fill up the table).
int newCapacity = capacity;

if (size > (capacity >> 1)) {
// More than 1/2 filled w/ live entries.
// Double size.
newCapacity = capacity * 2;
}

Object[] oldTable = this.table;

// Allocate new table.
initializeTable(newCapacity);

// We won't have any tombstones after this.
this.tombstones = 0;

// If we have no live entries, we can quit here.
if (size == 0) {
return true;
}

// Move over entries.
for (int i = oldTable.length - 2; i >= 0; i -= 2) {
Object k = oldTable[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}

// The table can only contain null, tombstones and references.
@SuppressWarnings("unchecked")
Reference<ThreadLocal<?>> reference
= (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
if (key != null) {
// Entry is still live. Move it over.
add(key, oldTable[i + 1]);
} else {
// The key was reclaimed.
size--;
}
}

return true;
}


可以看出该方法首先判断 当前已删除的实体与存放进来的实体是否大于当前ThreadLocal的存储阀值,如果小于该阀值 直接返回false,如果大于该阀值,可以看到代码中执行了一个table数组减小的操作

int capacity = table.length >> 1


然后对已存放的数据与 capacity>>1 进行比较当

if (size > (capacity >> 1)) {
// More than 1/2 filled w/ live entries.
// Double size.
newCapacity = capacity * 2;
}


则对该数据进行扩充。

之后可以看到大致流程是,获取到oldTable,并对其进行遍历,当遍历到某个位置 key!=null时将该value值存放到new出的新的table中,其内部主要看一些该方法

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


/**
* Adds an entry during rehashing. Compared to put(), this method
* doesn't have to clean up, check for existing entries, account for
* tombstones, etc.
*/
void add(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == null) {
table[index] = key.reference;
table[index + 1] = value;
return;
}
}
}


该方法顾名思义就是将 key -value 存放到Values的Object数组中去。

在线程数据存储时,不同的线程对用生成不同的key值,通过key.hash&mask 生成对应的index值,

然后将对应的key-value 存放到table的 index、index+1 这两个位置

到这可能会有一个疑问,假如:当A线程的reference与value 放到了 table的 1,2 位置,而线程B reference与value在table中的位置恰巧是2,3 位置,这样不就出现问题了吗?

接下看源码中是如何处理这个问题的。

/**
* Internal hash. We deliberately don't bother with #hashCode().
* Hashes must be even. This ensures that the result of
* (hash & (table.length - 1)) points to a key and not a value.
*
* We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
* every other bucket) to help prevent clustering.
*/
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);


通过上述的解释,我们可以看出 google 中保证了 (hash & (table.length - 1))该值始终是指向 key值,而不是value值。

private void initializeTable(int capacity) {
this.table = new Object[capacity * 2];
this.mask = table.length - 1;
this.clean = 0;
this.maximumLoad = capacity * 2 / 3; // 2/3
}


而table.length-1 我们通过代码可以看出 该值始终未奇数, 因此 通过 key.hash & mask 我们得出该值为0,而在next方法中

/**
* Gets the next index. If we're at the end of the table, we wrap back
* around to 0.
*/
private int next(int index) {
return (index + 2) & mask;
}


这样就形成了一个偶数串,从而就保证了 不同的线程在使用ThreadLocal存储时,其key的位置始终未偶数,value位置始终未奇数。

至此可以看出ThreadLocal是如何将key-value存放到对应的table中,同时保证了不同线程ThreadLocal的key值始终为偶数且在table中位于不同的位置。

现在回到rehash 方法中,在该方法中看到 只有

if (tombstones + size < maximumLoad) {
return false;
}


情况下返回false,其他情况直接返回true,对于cleanUp方法

if (rehash()) {
// If we rehashed, we needn't clean up (clean up happens as
// a side effect).
return;
}


内部执行了rehash()方法,该方法的解释是,如果执行了rehashed 那么是不需要clean up,因为在rehash中实质上是new 了一个新的table,同时将oldTable中key!=null的数据copy到了新的table中。所以不需要clean up

那么对于没有进行rehash()方法的table 在clean Up 方法内部看到实质就是将

if (reference.get() == null) {
// This thread local was reclaimed by the garbage collector.
table[index] = TOMBSTONE;
table[index + 1] = null;
tombstones++;
size--;
}


table数组中reference.get() == null 位置的 key-value 重新设置为 TOMBSTONE、null 这样便于在rehash方法执行时将这些数据删除。

执行完cleanUp()方法之后在回到 put()方法可以看到其内部 主要目的是当table [index]存在key值,那么[index+1]位置的value值将被覆盖,否则key-value 存放到对应的 [index]、[index+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;
}
}


阅读上述代码,第一次put时 firstTombstone 为-1 ,在第一次put之后,

if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}


firstTombstone = index

之后table的put()一直执行

table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;


以上几行代码。

至此ThreadLocal的put()方法已经解析完成,接下来看一下 ThreadLocal是如何从table数组中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);
}


对于ThreadLocal中get方法可以看到,当values!=null时,直接判断this.reference == table[index] 如果存在则直接将[index+1]位置的values返回,当values为null的情况下,调用initializeValues()方法创建一个新的Values对象,之后直接return values.getAfterMiss(this);

getAfterMiss() 具体源码:

/**
* Gets value for given ThreadLocal after not finding it in the first
* slot.
*/
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;
}

// 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;
}
}
}


可以看到其内部主要目的是先将key-value 赋值到 [index]、[index+1]位置,然后再重新获取该key对应的value值。

到这android sdk中的ThreadLocal.java源码中主要部分已经分析完成

注:该android sdk版本 -22
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android threadLoca