在瞭解ThreadLocal之前,一定要確定一個概念:ThreadLocal不是用來解決共享對象的多線程訪問問題的
那麼ThreadLocal在多線程的作用是什麼呢?從下面幾個方面來了解
ThreadLocal的作用
ThreadLocal可以理解爲:線程局部變量, 是每一個線程所單獨持有的。其他線程不能對其進行訪問, 通常是類中的 private static 字段,是對該字段初始值的一個拷貝,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯,
在併發編程的時候,成員變量如果不做任何處理其實是線程不安全的,各個線程都在操作同一個變量,顯然是不行的,並且我們也知道volatile這個關鍵字也是不能保證線程安全的。當然我們可以使用synchorinized 關鍵字來爲此變量加鎖,進行同步處理,從而限制只能有一個線程來使用此變量,但是加鎖會大大影響程序執行效率,此外我們還可以使用ThreadLocal來解決對某一個變量的訪問衝突問題。
當使用ThreadLocal維護變量的時候 爲每一個使用該變量的線程提供一個獨立的變量副本,即每個線程內部都會有一個該變量,這樣同時多個線程訪問該變量並不會彼此相互影響,因此他們使用的都是自己從內存中拷貝過來的變量的副本, 這樣就不存在線程安全問題,也不會影響程序的執行性能。
深入瞭解ThreadLocal使用
先不去分析源碼,僅僅寫個例子,根據例子學習ThreadLocal的簡單使用。
public class ThreadTest {
//定義一個全局ThreadLocal
public static ThreadLocal<String> locals= new ThreadLocal<String>();
//開啓三個線程
public void threadLocalTest(){
ThreadLocalTest test1 = new ThreadLocalTest("AA");
ThreadLocalTest test2 = new ThreadLocalTest("BB");
ThreadLocalTest test3 = new ThreadLocalTest("CC");
test1.start();
test2.start();
test3.start();
}
}
在線程裏往ThreadLocal塞值,再打印出
public class ThreadLocalTest extends Thread {
private int a = 5;
public ThreadLocalTest(String name) {
super(name);
}
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 5; i++) {
a += 1;
ThreadTest.locals.set(a + "");
Log.e(this.getName(), "value ===== " + ThreadTest.locals.get());
Thread.sleep(300);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
查看運行結果
10-24 10:31:46.340 9307-9350/com.t9.news E/AA: value ===== 6
10-24 10:31:46.340 9307-9351/com.t9.news E/BB: value ===== 6
10-24 10:31:46.343 9307-9352/com.t9.news E/CC: value ===== 6
10-24 10:31:46.640 9307-9350/com.t9.news E/AA: value ===== 7
10-24 10:31:46.641 9307-9351/com.t9.news E/BB: value ===== 7
10-24 10:31:46.644 9307-9352/com.t9.news E/CC: value ===== 7
10-24 10:31:46.940 9307-9350/com.t9.news E/AA: value ===== 8
10-24 10:31:46.941 9307-9351/com.t9.news E/BB: value ===== 8
10-24 10:31:46.944 9307-9352/com.t9.news E/CC: value ===== 8
10-24 10:31:47.240 9307-9350/com.t9.news E/AA: value ===== 9
10-24 10:31:47.241 9307-9351/com.t9.news E/BB: value ===== 9
10-24 10:31:47.244 9307-9352/com.t9.news E/CC: value ===== 9
10-24 10:31:47.540 9307-9350/com.t9.news E/AA: value ===== 10
10-24 10:31:47.541 9307-9351/com.t9.news E/BB: value ===== 10
10-24 10:31:47.545 9307-9352/com.t9.news E/CC: value ===== 10
看到每個線程的裏都有自己的String,並且互不影響----,不存在一個線程修改另一個線程中值得情況,對於同一個ThreadLocal對象而言,內部數據僅爲自己獨有,其他線程無法修改
ThreadLocal的理解
瞭解了ThreadLocal的使用,接下來肯定要看看源碼,分析內部實現方式。
就從ThreadLocal 幾個主要方法來學習
public T get()
public void set(T value)
public void remove()
get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本
首先我們來看一下ThreadLocal類是如何爲每個線程創建一個變量的副本的。
先來看get方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
//先獲取當前線程
Thread t = Thread.currentThread();
//然後getMap(t)方法獲取到一個map,類型爲ThreadLocalMap
ThreadLocalMap map = getMap(t);
//不爲空
if (map != null) {
//然後接着下面獲取到<key,value>鍵值對,注意這裏獲取鍵值對傳進去的是 this,而不是當前線程t。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//如果獲取成功,則返回value值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//否則調用setInitialValue方法返回value
return setInitialValue();
}
仔細看看每一步的操作:
- getMap(t)
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
//返回當前線程中的成員變量threadLocals
return t.threadLocals;
}
那麼繼續查看,進入Thread.class ,成員變量threadLocals是什麼
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是ThreadLocal類的一個內部類,我們繼續取看ThreadLocalMap的實現:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap的Entry繼承了WeakReference,並且使用ThreadLocal作爲鍵值
如果getMap爲null,則返回setInitialValue()
- setInitialValue()方法
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//就是如果map不爲空,就設置鍵值對,
if (map != null)
map.set(this, value);
else
//爲空,再創建Map
createMap(t, value);
return value;
}
- createMap(t, value)的實現
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
創建ThreadLocalMap對象並賦值給Thread中的threadLocals
經過這一系列流程,ThreadLocal是爲每個線程創建變量的副本就很清晰:
-
首先,在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值爲當前ThreadLocal變量,value爲變量副本(即T類型的變量)。
-
初始化Thread時,threadLocals爲空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變量爲鍵值,以ThreadLocal要保存的副本變量爲value,存到threadLocals。
-
然後在當前線程裏面,如果要使用副本變量,就可以通過get方法在threadLocals裏面查找。
總結
1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。
2。將一個共用的ThreadLocal靜態實例作爲key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然後在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作爲參數傳遞的麻煩
3.通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中
4.爲何threadLocals的類型ThreadLocalMap的鍵值爲ThreadLocal對象,因爲每個線程中可有多個threadLocal變量
5.在進行get之前,必須先set,否則會報空指針異常;如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。
因爲get方法中,getMap()默認爲null,則返回setInitialValue(),setInitialValue()方法中, T value = initialValue() 默認返回null,最終會報空指針異常