背景
突然被人問(wèn)到這個(gè)問(wèn)題,心想不是很簡(jiǎn)單嗎固灵!但是他又問(wèn)你提到ThreadMap捅伤,哪ThreadMap怎么解決hash沖突的!這還真把我問(wèn)住了巫玻。
原理
抱著虛心學(xué)習(xí)丛忆,不斷遺忘不斷再學(xué)習(xí)的態(tài)度,咱們又來(lái)看一次源碼仍秤。
1熄诡、Looper是怎么創(chuàng)建的
在Android里面最常見(jiàn)的就是我們主線(xiàn)程的Looper了,主線(xiàn)程的Looper是怎么創(chuàng)建徒扶?
我們?cè)趶淖烂纥c(diǎn)擊一個(gè)圖標(biāo),實(shí)際上是通過(guò)launchActivity通過(guò)startActivity啟動(dòng)Activity的根穷,
會(huì)一直走到instrumentation.execStartactivity,然后在里面會(huì)跨進(jìn)程調(diào)用ActivityTaskManager的startActivity姜骡,然后通過(guò)AMS去和Zygote進(jìn)程進(jìn)行socket通信,然后會(huì)foker一個(gè)進(jìn)程屿良,在這個(gè)進(jìn)程里面反射執(zhí)行ActivityThrea的mian方法圈澈。我們來(lái)看看這里有啥
ActiivityThread.java
我們可以看到很明顯的調(diào)用了
//初始化Lopper
Looper.prepareMainLooper()
//繼續(xù)調(diào)用了Looper的prepare。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// 我們看到了代碼進(jìn)入ThreadLocal
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
在上面的代碼中我們都是通過(guò)sThreadLocal 來(lái)獲取Looper的尘惧,如果已經(jīng)存在了Looper就會(huì)拋出異常康栈。
//一個(gè)靜態(tài)變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
我們繼續(xù)查看它的get()方法
//get 方法也很簡(jiǎn)單,沒(méi)有特別復(fù)雜的操作
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();
}
這里由于我們是第一次進(jìn)來(lái)map肯定是為空的,所以我們肯定會(huì)調(diào)用setInitialValue方法
我們來(lái)看看里面有啥
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;
}
這里還是會(huì)去判斷這個(gè)map啥么,我們?nèi)タ纯催@個(gè)map到底是啥登舞。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
很明顯這個(gè)map就是一個(gè)線(xiàn)程對(duì)象的成員變量,一個(gè)簡(jiǎn)單的變量沒(méi)啥大不了的悬荣,不過(guò)它的類(lèi)型為T(mén)hreadLocalMap菠秒,我們看看這個(gè)TreaLocalMap和普通map區(qū)別。
static class ThreadLocalMap {
//我們可以看到他的Key是一個(gè)弱引用氯迂!
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 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
* 這里也很好理解践叠,必須為2的倍數(shù)
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* 這里是容器
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//一個(gè)構(gòu)造方法
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);
}
}
和普通map區(qū)別也不大,里面是一個(gè)tab[]用來(lái)保存鍵值對(duì)嚼蚀,不過(guò)這個(gè)Entry的鍵值對(duì)他是一個(gè)弱引用禁灼,需要值得關(guān)注,我們知道GC回收的時(shí)候轿曙,會(huì)對(duì)軟應(yīng)用持有的對(duì)象進(jìn)行一次回收弄捕。所以這里如果key被回收了,value就是無(wú)效的拳芙,但是這個(gè)map又被線(xiàn)程一直持有察藐,所以會(huì)造成內(nèi)存泄露。
了解這個(gè)Map的結(jié)構(gòu)之后我們?cè)賮?lái)看這個(gè)
/////////////////////------ThreadLocal.java-----------------
private T setInitialValue() {
T value = initialValue(); // 這里就是一個(gè)null值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//調(diào)用構(gòu)造函數(shù)
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
這時(shí)候就直接調(diào)用構(gòu)造函數(shù)舟扎,我們知道了會(huì)構(gòu)造一個(gè)map分飞,并且執(zhí)行一次hash,把value設(shè)置為null睹限。
到這里我們就知道為什么只能保證一個(gè)了譬猫,因?yàn)長(zhǎng)ooper的創(chuàng)建只能是通過(guò)Looper.prepare()或者時(shí)Looper.prepareMain(),就會(huì)給調(diào)用這個(gè)方法的線(xiàn)程創(chuàng)建一個(gè)Looper羡疗,當(dāng)你想在創(chuàng)建的時(shí)候會(huì)判斷當(dāng)前線(xiàn)程對(duì)象的變量ThreadLocalMap染服,是否包含了Looper的Value,如果存在會(huì)拋出異常叨恨。這里的key就是sTrheadLocal柳刮。
這時(shí)候我們?cè)倩卮餞hreadLocalMap怎么解決hash沖突的?
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;
}
}
可以清楚的看到源碼里面是通過(guò)線(xiàn)性探測(cè)來(lái)解決的痒钝。不是很麻煩秉颗。
一般解決hash沖突有三種方法:線(xiàn)性探測(cè),再hash送矩,以及鏈表法蚕甥。
小貼士
線(xiàn)性探測(cè)是解決hash沖突的一種方式,比如一個(gè)key 的hash值為21栋荸,一個(gè)key的hash值為11菇怀,他們?cè)趯?duì)數(shù)組長(zhǎng)度11 (key.threadLocalHashCode & (len-1);)取模的時(shí)候凭舶,都會(huì)定位到1這個(gè)位置。
比如先put ke為21這個(gè)發(fā)現(xiàn)1這個(gè)位置沒(méi)有值爱沟,直接就放入了帅霜,這時(shí)候我們?cè)俜湃?1這個(gè)key,他會(huì)發(fā)現(xiàn)1這個(gè)位置已經(jīng)有值了钥顽,他會(huì)將下標(biāo)+1义屏,變?yōu)?這個(gè)位置,沒(méi)有值蜂大,直接放入闽铐。
我們從里面取出元素的時(shí)候,比如我們先get 11這個(gè)key奶浦,直接定位到1這個(gè)位置兄墅,發(fā)現(xiàn)這時(shí)候我們不能直接獲取value,先要比對(duì)equal方法澳叉,相等直接返回value隙咸,否則需要將下標(biāo)加1比對(duì)下一個(gè)位置。