線程本地變量:線程本地變量通常是一個類中的私有靜態(tài)的成員變量殉簸。我們可以在不同的線程中的調(diào)用線程本地變量的get
和set
方法,來獲取和設(shè)置當(dāng)前線程中線程本地變量的值。在當(dāng)前線程改變線程本地變量的值,并不會影響其他線程本地變量的值踩娘。
上面一段話有點抽象,先舉個例子說一下喉祭。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//注釋1處养渴,在主線程中設(shè)置ThreadLocal保存的變量值
threadLocal.set("初始名稱");
//注釋2處
System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get());
new TestThread("線程甲", threadLocal).start();
new TestThread("線程乙", threadLocal).start();
}
}
class TestThread extends Thread {
private ThreadLocal<String> threadLocal;
public TestThread(String name, ThreadLocal<String> threadLocal) {
super(name);
this.threadLocal = threadLocal;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 6) {
//當(dāng)i==6的時候替換成當(dāng)前線程名
threadLocal.set(getName());
}
//獲取
System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get() + ",i= " + i);
}
}
}
輸出如下
main ,初始名稱
線程甲 ,null泛烙,i= 0
線程甲 ,null厚脉,i= 1
線程甲 ,null,i= 2
線程甲 ,null胶惰,i= 3
線程甲 ,null,i= 4
線程甲 ,null霞溪,i= 5
線程甲 ,線程甲孵滞,i= 6
線程甲 ,線程甲,i= 7
線程甲 ,線程甲鸯匹,i= 8
線程甲 ,線程甲坊饶,i= 9
線程乙 ,null,i= 0
線程乙 ,null殴蓬,i= 1
線程乙 ,null匿级,i= 2
線程乙 ,null,i= 3
線程乙 ,null染厅,i= 4
線程乙 ,null痘绎,i= 5
線程乙 ,線程乙,i= 6
線程乙 ,線程乙肖粮,i= 7
線程乙 ,線程乙孤页,i= 8
線程乙 ,線程乙,i= 9
在這個例子中涩馆,我們創(chuàng)建了一個ThreadLocal變量行施,內(nèi)部保存的是一個String類型的變量允坚。
private static ThreadLocal<String> name = new ThreadLocal<>();
在main方法的注釋1處,我們在主線程中設(shè)置ThreadLocal變量值為初始名稱
蛾号,所以在注釋2處輸出如下
main ,初始名稱
然后我們新建了兩個線程稠项,在線程的run方法中首先從0到5threadLocal.get()
方法獲取的值都是null。
然后從6開始把threadLocal設(shè)置為當(dāng)前線程的名字鲜结,然后再調(diào)用threadLocal.get()
方法輸出的就是線程對應(yīng)的名字了展运。
源碼分析
首先我們看一下ThreadLocal的構(gòu)造方法
public ThreadLocal() {
}
看下ThreadLocal的set(T value)
方法相關(guān)操作
public void set(T value) {
Thread t = Thread.currentThread();
//注釋1處
ThreadLocalMap map = getMap(t);
//注釋2處,注意傳入的this是作為key的轻腺。
if (map != null)
map.set(this, value);
//注釋3處乐疆,
else
createMap(t, value);
}
在注釋1處,首先獲取當(dāng)前線程的ThreadLocalMap對象贬养。ThreadLocal的getMap方法挤土。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread類的threadLocals變量是一個ThreadLocal.ThreadLocalMap
對象,用來保存與當(dāng)前線程相關(guān)的ThreadLocal變量误算。
public class Thread implements Runnable {
//...
//用來保存與當(dāng)前線程相關(guān)的ThreadLocal變量
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal.ThreadLocalMap
是ThreadLocal的一個靜態(tài)內(nèi)部類仰美,只適合用來保存線程本地變量。ThreadLocalMap的實體使用弱引用來保存key儿礼。注意key是ThreadLocal<?> 咖杂。
static class ThreadLocalMap {
//初始容量,必須是2的冪
private static final int INITIAL_CAPACITY = 16;
//存儲entry的表蚊夫,根據(jù)需要調(diào)整大小诉字。表的長度必須是2的冪
private Entry[] table;
//...
static class Entry extends WeakReference<ThreadLocal<?>> {
//和ThreadLocal關(guān)聯(lián)的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
//使用弱引用來保存key
super(k);
value = v;
}
}
}
在set(T value)
方法的注釋3處,如果獲取到的map對象為null知纷,則調(diào)用createMap方法壤圃。
創(chuàng)建一個ThreadLocalMap對象賦值給當(dāng)前線程的threadLocals變量。同時保存了初始的key和value琅轧。注意key是ThreadLocal類型的
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap的兩個參數(shù)的構(gòu)造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//注釋1處伍绳,看一看 threadLocalHashCode 是怎么計算的。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//保存初始的key和value
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
注釋1處乍桂,threadLocalHashCode 是一個final類型的變量冲杀。而且是一個原子類型的變量。
private static AtomicInteger nextHashCode =
new AtomicInteger();
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
在set(T value)
方法的注釋2處睹酌,如果獲取到的map對象不為null权谁,則調(diào)用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法。
ThreadLocalMap的set(ThreadLocal<?> key, Object value)
方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//注釋1處憋沿,獲取在map中的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//更新key對應(yīng)的value
if (k == key) {
e.value = value;
return;
}
//替換指定位置上的鍵值對
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//加入到table中
tab[i] = new Entry(key, value);
int sz = ++size;
//是否需要縮減table或者擴容闯传,如果是擴容的話,容量會增加到兩倍
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set(ThreadLocal<?> key, Object value)方法,注釋1處甥绿,獲取在map中的位置字币,要注意一下這個key,threadLocalHashCode
是一個final 類型的變量共缕,在第一次創(chuàng)建ThreadLocal對象的時候初始化洗出,后面就不會變了。
接下來看一下ThreadLocal的get方法
public T get() {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取當(dāng)前線程的ThreadLocalMap對象map
ThreadLocalMap map = getMap(t);
if (map != null) {//如果map存在图谷,返回對應(yīng)的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//map不存在翩活。調(diào)用setInitialValue方法
return setInitialValue();
}
方法內(nèi)部判斷如果線程的threadLocals
不為null,就從中取出對應(yīng)的值并返回便贵。
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);
}
如果map中沒有對應(yīng)的值菠镇,返回setInitialValue
方法的執(zhí)行結(jié)果。
ThreadLocal的setInitialValue方法
private T setInitialValue() {
//注釋1處,首先調(diào)用initialValue方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
ThreadLocal的initialValue方法默認返回null
protected T initialValue() {
return null;
}
我們可以重寫ThreadLocal的initialValue方法來提供一個默認值承璃,就像這樣利耍。
private static ThreadLocal<String> name = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello world";
}
};
總結(jié):
- 實際的通過ThreadLocal創(chuàng)建的副本是存儲在每個線程自己的threadLocals中的。
- 如果想在調(diào)用get方法之前不需要調(diào)用set方法就想得到一個默認的值的話盔粹,可以重寫initialValue()方法隘梨。
Android 中的Looper 是使用ThreadLocal來保存的。
public static void prepare() {
prepare(true);
}
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));
}
參考鏈接: