ThreadLocal 類用來(lái)提供線程內(nèi)部的局部變量,并且這些變量依靠線程獨(dú)立存在.
可以在多個(gè)線程中互不干擾的進(jìn)行存儲(chǔ)數(shù)據(jù)和修改數(shù)據(jù)迎献,通過set,get 和remove方法瞎访,
每個(gè)線程都是獨(dú)立的操作.
里面的原理是:在不同的線程中訪問同一個(gè)對(duì)象,獲取到的值是不一樣的吁恍,因?yàn)閮?nèi)部會(huì)
從各種的線程中取出一個(gè)數(shù)組扒秸。 通過對(duì)應(yīng)的下標(biāo),查找對(duì)應(yīng)的Value值
下面看個(gè)簡(jiǎn)單的列子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("123");
Log.e("Charles2", "ss1==" + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
String s = threadLocal.get();
Log.e("Charles2", "ss2=" + s);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("234");
String s = threadLocal.get();
Log.e("Charles2", "ss3==" + s);
}
}).start();
}
打印的結(jié)果如下 :
07-18 15:58:14.312 21113-21113/? E/Charles2: ss1==123
07-18 15:58:14.326 21113-21140/? E/Charles2: ss2=null
07-18 15:58:14.327 21113-21141/? E/Charles2: ss3==234
大家都知道這三行打印都在不同的線程冀瓦,線程1是在主線程伴奥,線程2 和 線程3 分別在不各
自線程中,但是由于線程2沒有給它設(shè)置值所以取出來(lái)的是null.其它2個(gè)線程的內(nèi)部變量
都是有值.
二翼闽、實(shí)現(xiàn)原理
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對(duì)象之后,用的this作為key肄程,this在這里代表的是當(dāng)前線程的ThreadLocal對(duì)象锣吼。 另外就是第二句根據(jù)getMap獲取一個(gè)ThreadLocalMap选浑,其中g(shù)etMap中傳入了參數(shù)t(當(dāng)前線程對(duì)象),這樣就能夠獲取每個(gè)線程的ThreadLocal了玄叠。 繼續(xù)跟進(jìn)到ThreadLocalMap中查看set方法:
(2)1.ThreadLocalMap
ThreadLocalMap是ThreadLocal的一個(gè)內(nèi)部類古徒,在分析其set方法之前,查看一下其類結(jié)構(gòu)和成員變量读恃。
static class ThreadLocalMap {
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.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
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
然后看下ThreadLocal的構(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);
}
構(gòu)造函數(shù)的第一個(gè)參數(shù)就是本ThreadLocal實(shí)例(this)隧膘,第二個(gè)參數(shù)就是要保存的線程本地變量。構(gòu)造函數(shù)首先創(chuàng)建一個(gè)長(zhǎng)度為16的Entry數(shù)組寺惫,然后計(jì)算出firstKey對(duì)應(yīng)的哈希值疹吃,然后存儲(chǔ)到table中,并設(shè)置size和threshold西雀。
注意一個(gè)細(xì)節(jié)萨驶,計(jì)算hash的時(shí)候里面采用了hashCode & (size - 1)的算法,這相當(dāng)于取模運(yùn)算hashCode % size的一個(gè)更高效的實(shí)現(xiàn)(和HashMap中的思路相同)艇肴。正是因?yàn)檫@種算法腔呜,我們要求size必須是2的指數(shù),因?yàn)檫@可以使得hash發(fā)生沖突的次數(shù)減小再悼。
ThreadLocalMap#set
ThreadLocal中put函數(shù)最終調(diào)用了ThreadLocalMap中的set函數(shù)核畴,跟進(jìn)去看一看:
···
private void set(ThreadLocal key, Object value) {
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();
}
···
在上述代碼中如果Entry在存放過程中沖突了,調(diào)用nextIndex來(lái)處理冲九,如下所示谤草。是否還記得hashmap中對(duì)待沖突的處理?這里好像是另一種套路:只要i的數(shù)值小于len莺奸,就加1取值丑孩,官方術(shù)語(yǔ)稱為:線性探測(cè)法。
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
以上步驟ok了之后憾筏,再次關(guān)注一下源碼中的cleanSomeSlots嚎杨,該函數(shù)主要的作用就是清理無(wú)用的entry,具體細(xì)節(jié)就不扣了:
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
下面來(lái)看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)
return (T)e.value;
}
return setInitialValue();
}
如果map不會(huì)空氧腰,就根據(jù)當(dāng)前線程去查存儲(chǔ)的變量。
如果map為null刨肃,就返回setInitialValue()這個(gè)方法古拴,跟進(jìn)這個(gè)方法看一下:
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;
}
最后返回的是value,而value來(lái)自initialValue(),進(jìn)入這個(gè)源碼中查看:
protected T initialValue() {
return null;
}
原來(lái)如此真友,如果不設(shè)置ThreadLocal的數(shù)值黄痪,默認(rèn)就是null,來(lái)自于此盔然。