Android源碼解析Handler系列第(二)篇--- ThreadLocal詳解

轉(zhuǎn)載請(qǐng)注明文章出處LooperJing杯瞻!

在上篇文章Android源碼解析Handler系列第(一)篇說了Message的內(nèi)部維持的全局池機(jī)制。這一篇仍然是準(zhǔn)備知識(shí)允坚,因?yàn)樵贖andler中有ThreadLocal的身影茫多,大家知道,Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)魏保,那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢?這就要使用ThreadLocal了摸屠,ThreadLocal可以在不同的線程之中互不干擾地存儲(chǔ)并提供數(shù)據(jù)谓罗,通過ThreadLocal可以輕松獲取每個(gè)線程的Looper,所以季二,ThreadLocal是理解Looper的關(guān)鍵之一檩咱。

先看一下官方對(duì)這個(gè)類的解釋

/**
 * Implements a thread-local storage, that is, a variable for which each thread
 * has its own value. All threads share the same {@code ThreadLocal} object,
 * but each sees a different value when accessing it, and changes made by one                                                      
 * thread do not affect the other threads. The implementation supports
 * {@code null} values.
 *
 * @see java.lang.Thread                                                                                                                                                                                                                                                                                   
 * @author Bob Lee
 */

大概意思就是說:實(shí)現(xiàn)了一個(gè)線程本地存儲(chǔ)揭措,也就是說,每個(gè)線程的一個(gè)變量刻蚯,有自己的值绊含。所有線程共享同一個(gè) ThreadLocal對(duì)象,但每個(gè)線程訪問它時(shí)炊汹,看到一個(gè)不同的值躬充,如果一個(gè)線程改變了這個(gè)值,不影響其他線程讨便,ThreadLocal支持NULL值充甚。理解好費(fèi)勁,只能說我翻譯的太差霸褒!

重新解釋一下:當(dāng)工作于多線程中的對(duì)象使用ThreadLocal 維護(hù)變量時(shí)伴找,ThreadLocal 為 每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本。每個(gè)線程獨(dú)立改變自己的副本废菱,而不影響其他線程所對(duì)應(yīng)的變量副本技矮。這就是它的基本原理,ThreadLocal主要的 API很簡(jiǎn)單昙啄。

public void set(T value):將值放入線程局部變量中

public T get():從線程局部變量中獲取值

public void remove():從線程局部變量中移除值(有助于 JVM 垃圾回收)

protected T initialValue():返回線程局部變量中的初始值(默認(rèn)為 null)

ThreadLocal為各個(gè)線程保存一個(gè)變量副本穆役,這句話是關(guān)鍵,怎么理解梳凛?先來一個(gè)簡(jiǎn)單的DEMO

public class ThreadLocalTest {
  
    public static void main(String[] args) throws InterruptedException {
        
        
        ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<Boolean>(){
            
            @Override
            protected Boolean initialValue() {
            
                //初始值是false
                return false;
            }
        };
        mBooleanThreadLocal.set(true);
    
        System.out.println("[Thread#main]mBooleanThreadLocal=" +mBooleanThreadLocal.hashCode()+"  "+ mBooleanThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                System.out.println( "[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() +"  "+ mBooleanThreadLocal.get());
            };
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                System.out.println( "[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() + "  "+mBooleanThreadLocal.get());
            };
        }.start();
    }
}
輸出結(jié)果:

[Thread#main]mBooleanThreadLocal=366712642      true
[Thread#1]mBooleanThreadLocal=366712642      false
[Thread#2]mBooleanThreadLocal=366712642      false

在上面的代碼中耿币,在主線程中設(shè)置mBooleanThreadLocal的值為true,在子線程1中設(shè)置mBooleanThreadLocal的值為false韧拒,在子線程2中不設(shè)置mBooleanThreadLocal的值淹接,然后分別在3個(gè)線程中通過get方法去mBooleanThreadLocal的值,所以叛溢,主線程中應(yīng)該是true塑悼,子線程1中應(yīng)該是false,而子線程2中由于沒有設(shè)置值楷掉,所以應(yīng)該是初始值false厢蒜。

再看一個(gè)DEMO

public class ThreadLocalTest {
        
        //創(chuàng)建一個(gè)Integer型的線程本地變量
    public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[5];
        for (int j = 0; j < 5; j++) {       
               threads[j] = new Thread(new Runnable() {
                @Override
                public void run() {
                     //獲取當(dāng)前線程的本地變量,然后累加5次
                    int num = local.get();
                    for (int i = 0; i < 5; i++) {
                        num++;
                    }
                                        //重新設(shè)置累加后的本地變量
                    local.set(num);
                    System.out.println(Thread.currentThread().getName() + " : "+ local.get());

                }
            }, "Thread-" + j);
        }

        for (Thread thread : threads) {
            thread.start();
        }
    }
}
輸出結(jié)果:

Thread-1 : 5
Thread-3 : 5
Thread-4 : 5
Thread-0 : 5
Thread-2 : 5

開了5個(gè)線程烹植,每個(gè)線程累加后的結(jié)果都是5斑鸦,各個(gè)線程處理自己的本地變量值,線程之間互不影響草雕。

讀到這里巷屿,相信你對(duì)ThreadLocal的作用已經(jīng)了解了,ThreadLocal和synchronized是有區(qū)別的墩虹,概括起來說嘱巾,對(duì)于多線程資源共享的問題憨琳,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式旬昭。前者僅提供一份變量篙螟,讓不同的線程排隊(duì)訪問,而后者為每一個(gè)線程都提供了一份變量问拘,因此可以同時(shí)訪問而互不影響闲擦,所以ThreadLocal和synchronized都能保證線程安全,但是應(yīng)用場(chǎng)景卻大不一樣场梆。

現(xiàn)在從源碼中去找找答案,為什么各個(gè)線程都能保留一份副本纯路,做到多并發(fā)的時(shí)候或油,線程互不影響呢?

ThreadLocal的構(gòu)造函數(shù)是空的驰唬,啥也沒有顶岸。

public ThreadLocal() {
}

看一下里面的set方法

    /**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

首先獲得了當(dāng)前線程實(shí)例,將實(shí)例傳入values方法中叫编,獲得一個(gè)Values對(duì)象辖佣,第一次獲得的Values對(duì)象是空的,就調(diào)用initializeValues初始化一個(gè)values對(duì)象搓逾,最后把當(dāng)前對(duì)象作為key,value作為值卷谈,放進(jìn)values中。現(xiàn)在的重點(diǎn)就是搞懂Values是什么霞篡?Values原來是ThreadLocal的靜態(tài)內(nèi)部類世蔗。在ThreadLocal.Values中有一個(gè)table成員,而這個(gè)table就以key,value的形式存儲(chǔ)了線程的本地變量朗兵。key是ThreadLocal<T>類型的對(duì)象的弱引用污淋,而Value則是線程需要保存的線程本地變量T。

 /** 
  * 存放數(shù)據(jù)的數(shù)組余掖。他存放數(shù)據(jù)的形式有點(diǎn)像map,是ThreadLocal與value相對(duì)應(yīng), 長(zhǎng)度總是2的N次方
  */
 private Object[] table;

table表結(jié)構(gòu)

Value的主要API有下面幾個(gè)

  • void put(ThreadLocal<?> key, Object value):往table里添加一個(gè)鍵值對(duì)
  • void add(ThreadLocal<?> key, Object value):也是往table里面添加鍵值對(duì)
  • Object getAfterMiss(ThreadLocal<?> key):在首位置沒找到值的時(shí)候通過這個(gè)方法來找到給定key的值
  • void remove(ThreadLocal<?> key):刪掉給定key對(duì)應(yīng)的值


    ThreadLocal.Values的其他成員

Values是調(diào)用put方法把傳進(jìn)來的鍵值對(duì)給存到table表里面去寸爆,這里不去分析它是怎么實(shí)現(xiàn)的了(可以移步http://blog.csdn.net/luoyanglizi/article/details/51510233

 /**
         * Sets entry for given ThreadLocal to given value, creating an
         * entry if necessary.
         */
        void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

上面是ThreadLocal存的過程,現(xiàn)在看取的過程盐欺。

    /**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

首先取出當(dāng)前線程的Values對(duì)象赁豆,跟set方法一樣,如果這個(gè)對(duì)象為null那么就返回初始值找田, 如果Values對(duì)象不為null挑势,那就取出它的table數(shù)組并找出ThreadLocal的reference對(duì)象在table數(shù)組中的位置悉抵,然后table數(shù)組中的下一個(gè)位置所存儲(chǔ)的數(shù)據(jù)就是ThreadLocal的值。從可以看出档冬,ThreadLocal的set和get方法所操作的對(duì)象都是當(dāng)前線程的Values對(duì)象的table數(shù)組,如果一個(gè)變量使用了ThreadLocal,通過ThreadLocal的set和get方法,每一個(gè)線程的都會(huì)有一份這個(gè)變量的副本(鍵值對(duì)的形式進(jìn)行存惹陡佟),這就是為什么多線程并發(fā)的時(shí)候,線程互不影響的操作一個(gè)變量准谚。

最后在舉一個(gè)ThreadLocal的應(yīng)用的例子:JDBC連接mysql數(shù)據(jù)庫,把 Connection 放到了 ThreadLocal 中去扣,這樣每個(gè)線程之間就隔離了柱衔,不會(huì)相互干擾。

public class DBUtil {
    // 數(shù)據(jù)庫配置
    private static final String driver = "com.mysql.jdbc.Driver";
    private static final String url = "jdbc:mysql://localhost:3306/demo";
    private static final String username = "xxx";
    private static final String password = "xxx";

    // 定義一個(gè)用于放置數(shù)據(jù)庫連接的局部線程變量(使每個(gè)線程都擁有自己的連接)
    private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();

    // 獲取連接
    public static Connection getConnection() {
        Connection conn = connContainer.get();
        try {
            if (conn == null) {
                Class.forName(driver);
                conn = DriverManager.getConnection(url, username, password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            connContainer.set(conn);
        }
        return conn;
    }

    // 關(guān)閉連接
    public static void closeConnection() {
        Connection conn = connContainer.get();
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            connContainer.remove();
        }
    }
}

Please accept mybest wishes for your happiness and success

參考鏈接:
https://my.oschina.net/clopopo/blog/149368
http://blog.csdn.net/singwhatiwanna/article/details/48350919
https://my.oschina.net/huangyong/blog/159725

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愉棱,一起剝皮案震驚了整個(gè)濱河市唆铐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奔滑,老刑警劉巖艾岂,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異朋其,居然都是意外死亡王浴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門梅猿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氓辣,“玉大人,你說我怎么就攤上這事袱蚓〕ィ” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵喇潘,是天一觀的道長(zhǎng)爽撒。 經(jīng)常有香客問我,道長(zhǎng)响蓉,這世上最難降的妖魔是什么硕勿? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮枫甲,結(jié)果婚禮上源武,老公的妹妹穿的比我還像新娘。我一直安慰自己想幻,他們只是感情好粱栖,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脏毯,像睡著了一般闹究。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上食店,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天渣淤,我揣著相機(jī)與錄音赏寇,去河邊找鬼。 笑死价认,一個(gè)胖子當(dāng)著我的面吹牛嗅定,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播用踩,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼渠退,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了脐彩?” 一聲冷哼從身側(cè)響起碎乃,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惠奸,沒想到半個(gè)月后荠锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晨川,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了删豺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共虑。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呀页,靈堂內(nèi)的尸體忽然破棺而出妈拌,到底是詐尸還是另有隱情,我是刑警寧澤蓬蝶,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布尘分,位于F島的核電站,受9級(jí)特大地震影響丸氛,放射性物質(zhì)發(fā)生泄漏培愁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一缓窜、第九天 我趴在偏房一處隱蔽的房頂上張望定续。 院中可真熱鬧,春花似錦禾锤、人聲如沸私股。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倡鲸。三九已至,卻和暖如春黄娘,著一層夾襖步出監(jiān)牢的瞬間峭状,已是汗流浹背克滴。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宁炫,地道東北人偿曙。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羔巢,于是被迫代替她去往敵國和親望忆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容