Android筆記——ThreadLocal原理淺析

復習和回顧Android知識蘸泻,梳理筆記

ThreadLocal簡介

ThreadLocal一般在開發(fā)中不是很常見诵棵,但是了解過Android消息機制的應該都看過Handler及Looper的源碼尘应,在Looper源碼里有出現過:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal源碼給出的定義(英文翻譯的有點渣):一個為線程本地提供數據的類箕肃,這些數據從每個線程獲取到的都是不同的茉帅,每個線程獨立初始化這些數據的副本齐佳。 簡單來說就是ThreadLocal為不同線程存儲數據,并且每個線程存儲的數據是獨立的填硕,修改互不影響其他線程麦萤。

舉個栗子

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import player.video.wzh.com.videoplayer.R;

public class ThreadLocalAct extends FragmentActivity {
    public String TAG = "wenzhihao";
    /**
     * 定義ThreadLocal
     */
    private ThreadLocal<Student> threadLocal = new ThreadLocal(){
        @Override
        protected Student initialValue() {
            return new Student("NULL");
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_threadlocal);
        /**
         * 在主線程設置一個對象
         */
        threadLocal.set(new Student("張三"));
        initTest();
    }

    private void initTest() {
        Log.e(TAG,Thread.currentThread().getName()+"-->"+threadLocal.get().toString());
        /**
         * 開啟一個子線程獲取Student
         */
        new Thread(){
            @Override
            public void run() {
                super.run();
                Log.e(TAG,Thread.currentThread().getName()+"-->"+threadLocal.get().toString());
            }
        }.start();
        /**
         * 開啟一個子線程再set一個Student
         */
        new Thread(){
            @Override
            public void run() {
                super.run();
                threadLocal.set(new Student("麗麗"));
                Log.e(TAG,Thread.currentThread().getName()+"-->"+threadLocal.get().toString());
            }
        }.start();

        Log.e(TAG,Thread.currentThread().getName()+"-->"+threadLocal.get().toString());
    }

    public class Student{
        public Student(String name) {
            this.name = name;
        }
        public String name ;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

運行結果(結果具有隨機性鹿鳖,線程非順序執(zhí)行):

E/wenzhihao: main---------Student{name='張三'}
E/wenzhihao: Thread-1200---------Student{name='NULL'}
E/wenzhihao: main---------Student{name='張三'}
E/wenzhihao: Thread-1201---------Student{name='麗麗'}

這這個栗子首先創(chuàng)建一個ThreadLocal,并重新了initialValue方法壮莹,如果不重寫此方法翅帜,上來就get會報錯,等于給它初始化命满。在主線程去set一個Student對象涝滴,然后開啟2個子線程,一個線程去直接從ThreadLocal取數據胶台,一個線程先設置另一個Student對象再去取數據歼疮,最后在主線程打印取出的數據。

結果證明在一個線程存儲數據只能在當前線程有效诈唬,并不影響其他線程的數據韩脏。

源碼分析

ThreadLocal<T>存儲的是數據,創(chuàng)建的時候需要為其指定泛型铸磅,最核心的方法應該就是set和get了赡矢,set是往當前線程保存數據,get是從當前線程獲取數據阅仔。

T 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();
    //ThreadLocalMap 是一個內部類吹散,內部有一個數組,數組里面存儲的是Entry霎槐,該類繼承自弱引用,
    //內部是以鍵值對形式存儲數據送浊,鍵就是ThreadlLocal,值就是要存儲的對象丘跌,每個線程都定義有這么一個對象
    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;
        }
    }
    //取出當前線程保存的數據返回袭景,否則就創(chuàng)建初始值
    //setInitialValue()內部調用initialValue() ,這里看出我們?yōu)槭裁匆趧?chuàng)建時候重寫initialValue()方法了闭树,不重寫默認返回null

    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) ——獲取該線程的ThreadLocalMap 對象

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
//每個線程都持有這么一個對象
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
 }

void set(T value) ——為該線程存儲數據

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
//獲取當前線程的ThreadLocalMap耸棒,把數據對象存到這個數據結構中

void remove()——移除當前線程ThreadLocal存儲的數據

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * {@code initialValue} method in the current thread.
 *
 * @since 1.5
 */
//獲取當前線程的ThreadLocalMap,把數據對象從改線程移除掉
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

通過源碼分析可以看出报辱,其實ThreadLocal在每個線程的數據操作与殃,都是在操作每個線程的ThreadLocalMap ,每個線程都有一個ThreadLocalMap 碍现,每個ThreadLocalMap 都有一個table數組存儲著數據幅疼,set和get操作其實就是在這個table數組進行數據的操作(table數組存儲的對象是Entry虛引用以鍵值對存儲數據,鍵——ThreadLocalMap 自身昼接,值——存儲的對象)爽篷,所以ThreadLocal可以在多個線程中互不干擾的操作自己的數據。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末慢睡,一起剝皮案震驚了整個濱河市逐工,隨后出現的幾起案子铡溪,更是在濱河造成了極大的恐慌,老刑警劉巖泪喊,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棕硫,死亡現場離奇詭異,居然都是意外死亡袒啼,警方通過查閱死者的電腦和手機哈扮,發(fā)現死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚓再,“玉大人灶泵,你說我怎么就攤上這事《酝荆” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵髓棋,是天一觀的道長实檀。 經常有香客問我,道長按声,這世上最難降的妖魔是什么膳犹? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮签则,結果婚禮上须床,老公的妹妹穿的比我還像新娘。我一直安慰自己渐裂,他們只是感情好豺旬,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柒凉,像睡著了一般族阅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膝捞,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天坦刀,我揣著相機與錄音,去河邊找鬼蔬咬。 笑死鲤遥,一個胖子當著我的面吹牛,可吹牛的內容都是我干的林艘。 我是一名探鬼主播盖奈,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼北启!你這毒婦竟也來了卜朗?” 一聲冷哼從身側響起拔第,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎场钉,沒想到半個月后蚊俺,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡逛万,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年泳猬,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宇植。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡得封,死狀恐怖,靈堂內的尸體忽然破棺而出指郁,到底是詐尸還是另有隱情忙上,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布闲坎,位于F島的核電站疫粥,受9級特大地震影響,放射性物質發(fā)生泄漏腰懂。R本人自食惡果不足惜梗逮,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绣溜。 院中可真熱鬧慷彤,春花似錦、人聲如沸怖喻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锚沸。三九已至艘虎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咒吐,已是汗流浹背野建。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恬叹,地道東北人候生。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像绽昼,于是被迫代替她去往敵國和親唯鸭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內容