前言
都知道ThreadLocal
能夠保存變量的副本膀曾,使得各個(gè)線程的訪問不受影響,今天看看是如何做到這一點(diǎn)的。
使用
先來看一個(gè)ThreadLocal
的簡單用法
// 定義一個(gè)ThreadLocal變量,存儲類型是Integer
private static final ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主線程set
mThreadLocal.set(1);
System.out.println(Thread.currentThread().getName() + "——>" + mThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
super.run();
// 先讀取
System.out.println(this.getName() + "——>" + mThreadLocal.get());
// 子線程set
mThreadLocal.set(2);
System.out.println(this.getName() + "——>" + mThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
super.run();
// 子線程get
System.out.println(this.getName() + "——>" + mThreadLocal.get());
}
}.start();
}
上面的例子很簡單,聲明了ThreadLocal
變量紧唱,存儲類型是Integer, 在main函數(shù)中,先set(1)隶校,并讀取打印漏益。隨后啟動兩個(gè)子線程,分別操作這個(gè)ThreadLocal
變量深胳。我們來看一下這個(gè)程序的運(yùn)行結(jié)果绰疤。
main——>1
Thread#1——>null
Thread#1——>2
Thread#2——>null
從運(yùn)行結(jié)果來看,各個(gè)線程對ThreadLcoal
變量的操作舞终,只在其線程內(nèi)部生效轻庆,并不會影響其它線程癣猾,接下來我們看看這點(diǎn)是如何做到的。
源碼分析
先看看類的定義
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
public class ThreadLocal<T> {}
從注釋就可以看出余爆,關(guān)鍵的方法是set
和get
纷宇,拿我們就直奔主題。
set方法
public void set(T value) {
Thread t = Thread.currentThread(); // 首先拿到當(dāng)前的線程對象
ThreadLocalMap map = getMap(t); // 拿到當(dāng)前線程對象中的一個(gè)ThreadLocalMap實(shí)例
if (map != null) { // 操作map對象
map.set(this, value);
} else {
createMap(t, value);
}
}
這個(gè)方法最終是將value值存儲到一個(gè)ThreadLocalMap
對象中蛾方,key是當(dāng)前當(dāng)前ThreadLocal
對象像捶,value為實(shí)際值。
接下來看看ThreadLocalMap map = getMap(t);
這句話
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
返回的是Thread
對象中的變量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
這里注意ThreadLocalMap
是定義在ThreadLocal
中的一個(gè)靜態(tài)類桩砰,他并不是我們廣義理解的Map拓春,看看他的定義。
static class ThreadLocalMap {
// 內(nèi)部繼續(xù)定一個(gè)Entry, 封裝key value
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table; // 一個(gè)Entry數(shù)組
private int size = 0;
……
}
ThreadLocalMap
內(nèi)部又定一個(gè)Entry類亚隅,并聲明一個(gè)Entry數(shù)組硼莽;因此我們應(yīng)該清楚了此map非彼map。
那么繼續(xù)看看這個(gè)所謂的map是如何做set處理的煮纵。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 拿到這個(gè)ThreadLocal的一個(gè)hash值
// 處理hash沖突懂鸵,這里使用簡單的線性hash解決hash沖突
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 說明是重置,直接修改當(dāng)前entry的value即可
if (k == key) {
e.value = value;
return;
}
// 說明已經(jīng)被回收了行疏,需要啟動清理矾瑰,key是弱引用
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 在當(dāng)前位置插入元素
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) // 擴(kuò)容處理
rehash();
}
到這里我們應(yīng)該清楚了set方法干得事,也應(yīng)該明白了隘擎,為什么
ThreadLocal
變量做到了線程數(shù)據(jù)備份,并且線程之間不會相互影響凉夯。
- 因?yàn)槊看握{(diào)用set货葬,都是操作的當(dāng)前線程中的
ThreadLocalMap
對象。
get方法
其實(shí)了解了set的實(shí)現(xiàn)機(jī)制劲够,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方法同樣是拿到當(dāng)前線程的ThreadLocalMap對象,在該對象上去查找當(dāng)前ThreadLocal
變量的存儲value征绎。
這里我們重點(diǎn)分析一下ThreadLocalMap.Entry e = map.getEntry(this);
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); // 計(jì)算下標(biāo)
Entry e = table[i]; // 從table數(shù)組中獲取Entry
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
結(jié)論
ThreadLocal
變量在賦值(set)和讀榷捉恪(get)的時(shí)候都先拿到了當(dāng)前線程中的ThreadLocalMap
對象,該對象是當(dāng)前的線程的成員變量人柿;這回答了ThreadLocal
能夠?qū)崿F(xiàn)線程數(shù)據(jù)備份的原因柴墩。ThreadLocalMap
并不是平時(shí)所說的Map結(jié)構(gòu),他是ThreadLocal
的靜態(tài)內(nèi)部類凫岖;并且在其內(nèi)部還定義了Entry數(shù)據(jù)結(jié)構(gòu)和一個(gè)Entry數(shù)組江咳。set和get都作用在這個(gè)數(shù)組之上。今天簡單的看了set和get方法哥放,以及其中牽扯到的一些東西歼指。其中還有許多其它值得探究的問題爹土,比如內(nèi)存泄漏,map中的value是強(qiáng)引用踩身。還有hash計(jì)算等胀茵。
ThreadLocal
中的內(nèi)存泄露推薦面試必備:ThreadLocal詳解
水平有限,若有不當(dāng)挟阻,請指出G砟铩!赁濒!