本篇文章首先給出了ThreadLocal常用的方法采够,并舉例來說明ThreadLocal的使用。然后分析了ThreadLocal的源代碼中的get()方法冰垄,初始化方法蹬癌,ThreadLocalMap的實現(xiàn)等來理解ThreadLocal的原理。
ThreadLocal為每個線程提供一個獨立的對象副本虹茶,每個線程可以單獨訪問自己獨有的對象逝薪,不存在多線程同時訪問一個對象時的共享問題。
一.使用ThreadLocal
ThreadLocal是一個泛型類蝴罪,使用時要指定持有的對象的類型董济。一般會重寫initialValue()方法,來設置線程第一次通過ThreadLocal獲取對象時的初始值要门。
常用方法:
\\1.get()方法 獲取對象引用虏肾,每個線程獲取到的不同
public T get();
\\2.set()方法廓啊,修改當前線程在ThreadLocal中存儲的值
public void set(T value);
\\3.initialValue()方法,設置初始值封豪,默認返回null崖瞭,一般被重寫來設置初始值
protected T initialValue();
具體例子
public class ThreadLocalDemo {
static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println(threadLocal.get());
}
});
thread1.start();
thread1.join();
System.out.println(threadLocal.get());
}
}
輸出結果:
Thread-0
main
可以看到,主線程和thread1通過threadLocal變量分別持有了各自的String對象撑毛。
二.源碼分析
get方法的主要流程
我們從get方法入手书聚,看看ThreadLocal的實現(xiàn) 原理,先po上源代碼:
public T get() {
//先獲取當前線程
Thread t = Thread.currentThread();
//獲取當前線程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//從ThreadLocalMap中獲取到要的值藻雌,注意這里使用的是當前的ThreadLocal對象作為key的
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//當前線程的ThreadLocalMap為null雌续,或者map里沒有保存和當前ThreadLocal相關的值時
return setInitialValue();
}
從代碼中可以總結出TheadLocal.get的流程:
- 先獲取到當前的線程t,
- 獲取到t中保存的ThreaaLocalMap胯杭,如果沒有則轉到最后一步
- 以當前ThreadLocal作為鍵值驯杜,從上述map中獲取到要的變量
- 如果獲取到變量為null,則轉到最后一步進行初始化
- 初始化thread的map或保存的變量值
查看getMap(t):從線程中獲取ThreadLocalMap的代碼發(fā)現(xiàn):Thread類中有一個名為threadLocals的ThreadLocalMap類型的變量做个,初始值為null鸽心,在第一次ThreadLocal.get時初始化。getMap會獲取這個變量居暖。
setInitialValue方法分析
下面再分析 一下setInitialValue()
private T setInitialValue() {
//獲取初始值顽频,這里調(diào)用的方法常常被重寫,來設置初始值太闺。默認返回Null
T value = initialValue();
//獲取當前線程
Thread t = Thread.currentThread();
//先嘗試獲取map糯景,因為也不知道是因為沒有map還是因為map中沒有值才進來這個方法的
ThreadLocalMap map = getMap(t);
//map存在,放入value值省骂,注意這里的key是當前ThreadLocal對象蟀淮;和get中的map.getEntry相呼應
if (map != null)
map.set(this, value);
else
//map不存在,創(chuàng)建map
createMap(t, value);
return value;
}
看一下createMap的代碼:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
直接調(diào)用了ThreadLocalMap的構造方法钞澳,將其初始化并返回怠惶。這個構造方法不再細究了,只要記住轧粟,傳入的兩個參數(shù)是用來作為初始化后的第一個鍵值對存入該map的即可策治。
總結一下setInitialValue()方法:
- 獲取value的初始化值,從initialValue方法逃延,這里就看出initialValue的作用了
- 如果Thread有ThreadLocalMap览妖,則直接使用轧拄,存入當前ThreadLocal->value的鍵值對
- 如果沒有map揽祥,則創(chuàng)建map,并同時存入鍵值對
- 返回value
ThreadLocalMap簡析
ThreadLocalMap是ThreadLocal的一個內(nèi)部類檩电。在每一個Thread都持有一個ThreadLocalMap變量(如果該線程沒用的ThreadLocal則為null)拄丰。它是一個map府树,key是ThreadLocal類型,value是保存的值料按。
這樣設計就保證了一個Thread可以存儲多個ThreadLocal奄侠。
ThreadLocalMap的Entry設計:
一個map中肯定有一個Entry來存儲鍵值對,ThreadLocal的Entry的設計的較為特殊:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到它繼承了WeakReference载矿。我們知道WeakReference是四大引用的一種叫做弱引用垄潮,它的含義是當對象只被弱引用引用時,下次GC時該對象就會被回收闷盔。
Q1:ThreadLocalMap的Entry為什么繼承WeekReference?
(個人理解)這樣當ThreadLocal變量生命周期結束后弯洗,對應的線程的ThreadLocalMap中存儲的相應鍵值對也會被回收,不會造成內(nèi)存泄露逢勾。
本文是Java并發(fā)專題(歡迎大家關注)的一篇牡整。
以下是完整的目錄:
Java并發(fā)之基礎知識
Java并發(fā)之volatile關鍵字
Java并發(fā)之synchronized關鍵字
Java并發(fā)之原子類
Java并發(fā)之線程池
Java并發(fā)之并發(fā)工具類
Java并發(fā)之AQS原理
Java并發(fā)之ThreadLocal使用和源碼分析