ThreadLocal部分源码解析
最近在整理Handler相关的东西,看到ThreadLocal,感觉源码不是太多,就拿过来啃啃,其实基本上ThreadLocal的原理是了解的,只是看了源码后就感觉更明了了一些。
先贴张图吧:
ThreadLocal图解
撩源码之前,先来看下ThreadLocal的原理:
一个ThreadLocal实例可以很方便的在任何地方存储或这获取当前线程的值,之前看到有人提到共享对象,其实这里和共享没有一分钱关系。如图所示,每个线程内部都持有一个ThreadLocalMap实例,而这个ThreadLocalMap实例用来干什么呢?就是用来存储ThreadLocal实例set(...)的值。可以看到ThreadLocalMap中持有一个数组,这个数组对应着多个ThreadLocal实例设置的值。比如说我有threadLocal1和threadLocal2,他俩都在线程1上set了一下。那么,ThreadLocalMap中的这个数组里就会出现两条记录。一条记录的key是threadLocal1,另一条记录的key是threadLocal2。 ThreadLocal取值的时候也很简单,只要去遍历下上图中的数组,对比数组中每一项的key和ThreadLocal实例就可以了。
先看个简单的小例子:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ThreadLocal<String> mDemoThreadLocal = new ThreadLocal();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDemoThreadLocal.set("main");
Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释1
new Thread(new Runnable() {
@Override
public void run() {
mDemoThreadLocal.set("Thread 1");
Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释2
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
mDemoThreadLocal.set("Thread 2");
Log.d(TAG, "onCreate: " + mDemoThreadLocal.get()); // 注释3
}
}).start();
}
}
用过ThreadLocal的同学都知道,执行一下的话,//注释1
标记的地方会打印出main
,//注释2
标记的地方会打印出Thread 1
,//注释3
标记的地方会打印出Thread 2
。这就是ThreadLocal的简单用法。
那么我们调用ThreadLocal的set(...)方法的时候,它背后到底干了什么呢?
public void set(T value) {
// 1 先获取当前线程
Thread t = Thread.currentThread();
// 2 根据当前线程,获取到一个ThreadLocalMap实例,
// ThreadLocalMap是定义在ThreadLocal里的一个内部类,
// 具体的存储就是它来实现的。
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// 4 获取之前该线程上保存的ThreadLocalMap的引用。
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 3. 创建一个ThreadLocalMap,把当前ThreadLocal实例作为key,
// 把值设置进去。并把这个ThreadLocalMap的引用丢给线程t。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
上面的源码比较简单,当线程中取到的ThreadLocalMap为null的时候:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 这里的table就是一开始图中所说的数组。Entry用来存储具体的值
table = new Entry[INITIAL_CAPACITY];
// 通过ThreadLocal实例的threadLocalHashCode变换得到具体的索引。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
就去实例化一个ThreadLocalMap并将引用赋给当前线程。ThreadLocalMap初始化的时候也会创建一个数组,具体的key和value设置给Entry之后就保存到这个数组中去了。
看下当线程中的ThreadLocalMap已经存在的时候的处理:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这里就是根据ThreadLocal的hashCode变换后得到一个索引。这里并不是直接把key和value作为新元素直接添加到table
里去的。因为table
初始化的时候有个默认空间,它会先去遍历table
中的元素,如果key之前已经存在了,则直接覆盖掉,如果遍历到一个key为null,则直接用新的key和value把这一项替换掉,这也是为了不浪费空间,因为有些key,value可能已经失效了。
下面再看下ThreadLocal的get()的处理逻辑
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();
}
有了前面的讲解后,get()
就简单多了。
- 获取到当前线程
2.获取当前线程上的ThreadLocalMap实例
3.根据TreadLocal实例获取到具体的key,value所在的Entry
4.取到Entry中的value
下面看下map.getEntry(this)
:
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);
}
这里有两部分处理,先根据ThreadLocal实例的hasCode变换的到索引。如果该索引对应的Entry有值,并且key也一致,就直接返回Entry。否则的就执行项目擦除,也就是getEntryAfterMiss(...)
中要做的工作了。
这一小部分源码基本上就能看出ThreadLocal的工作原理了,当然ThreadLocalMap里还有几个比较大的扩展数组,擦除元素等等方法也是很值得撩一撩的。
如文中理解有错误,请大家不吝指出,一起探讨。