概述
这里是基于java JDK1.8源码分析的。
首先从整体上描述一下ThreadLocal:
ThreadLocal中的ThreadLocalMap静态内部类使用的是线性探测表(散列表)作为数据结构。
每一个Thread对象都持有一个ThreadLocalMap,该Map以ThreadLocal对象的引用作为Key来查询/保存/删除Value。
一个线程中可以使用多个ThreadLocal对象来保存不同的数据,不同的线程可以使用相同的ThreadLocal对象作为Key,但是由于每个线程持有的ThreadLocalMap对象不同,所有相同Key对应的Value不同。做个类比,每个线程对应不同的学生,ThreadLocal是一个个页码,线程持有的ThreadLocalMap是不同学生持有不同版本的字典,当老师说了一个页码以后,不同学生在自己手上独一无二的字典中查找到的内容当然不同。
源码分析
构造器
构造器中并没有做什么
public ThreadLocal() {
}
get方法
ThreadLocal的get方法回返回当前线程中该ThreadLocal实例对应保存的数据。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
方法中,首先获取当前线程t,然后得到t的ThreadLocalMap。ThreaLocalMap是ThreadLocal的内部类,我们看一下它的主要成员:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
.....
}
内部有一个继承了弱引用的Entry静态类,我们可以看到Entry内部其实是Key为弱引用,关于会造成的内存泄露的分析,大家可以看一下这篇文章
接着上面所说,如果该Map不为空,则以该ThreadLocal实例的引用作为Key在该Map中寻找Entry并返回Value;如果Map为空则调用setInitialValue方法返回数据。下面我们看一下getEntry方法:
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
由于ThreadLocalMap内部是用线性探测的散列表实现的(底层维护一个Entry数组table[],区别于HashMap等容器的拉链表),这里很清楚地看到由key的hashcode&数组长度 得到下标之后,分为查找中和没有查找中两种情况,这里不再详细说明了。我们回到上面,如果Map为空,则调用setInitialValue方法返回数据,我们看一下该方法:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
首先调用initialValue方法获得了一个Value
protected T initialValue() {
return null;
}
该方法为空,一般是子类继承ThreadLocal类重写这个方法返回一个自己想保存的数据的初始值。回到setInitialValue方法中,接下来也是获得当前线程,再获得ThreadLocalMap,如果Map不空,进行set;如果Map空,新建Map:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在这个方法中我们为Thread的ThreadLocal.ThreadLocalMap引用新建一个对象:
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
这里我们把调用get方法的ThreadLocal对象和在initialValue方法中返回的值作为一个Entry存入线性探测表中。
我们总结一下,当调用了某个ThreadLocal.get()后:
1.查找当前线程是否具有ThreadLocalMap,有进入2,没有进入3.
2.在Map中以该ThreadLocal对象为Key查找Value并返回(涉及到线性探测表的查找)。
3.以该ThreadLocal对象为Key,initialValue方法中返回值作为Value作为一个Entry,为当前线程初始化一个ThreadLocalMap。
set方法
ThreadLocal的set方法为当前线程设置数据。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
依旧是和Map相关的,我们看一下ThreadLocalMap的set操作,这里依旧涉及到了线性探测表这个数据结构的set操作,我们就不具体展开了。
优势
- 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)
- 当Thread销毁之后,其持有的ThreadLocalMap也就随之销毁了,能减少内存使用量。
Android中的ThreadLocal
与jdk1.8中的略有不同,下面粗略地看一下
静态内部类Values
底层也是维护了一个数组
static class 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;
.....
}
看一下它的put方法,没有具体细看,但是注意到key和value是前后放入table数组中的:
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;
}
}
}
同样,在Thread类中,维护一个Values成员:
ThreadLocal.Values localValues;
set方法
根据当前线程去获取其Values,如果values为空,为values初始化,否则以该ThreadLocal实例为key,传入参数为value,前后一起放入数组中。
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
/**
* Gets Values instance for this thread and variable type.
*/
Values values(Thread current) {
return current.localValues;
}
/**
* Creates Values instance for this thread and variable type.
*/
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
get方法
也是获取到当前Thread的Values对象,如果以当前ThreadLocal对象为key找到了对应的value则返回,否则调用getAfterMiss方法,这个方法没有细看,应该也是使用了线性探测法去找key。
@SuppressWarnings("unchecked")
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的总结请看开头。
女票良心美代,只做正品的搬运工。如果你觉得这篇文章帮助到了你一点点,扫码支持一下吧^ ^