Android ThreadLocal及InheritableThreadLocal分析


因為在android中經(jīng)常用到handler來處理異步任務(wù),通常用于接收消息但绕,來操作UIThread救崔,其中提到涉及到的looper對象就是保存在Threadlocal中的,因此研究下Threadlocal的源碼捏顺。

分析都是基于android sdk 23 源碼進行的六孵,ThreadLocal在android和jdk中的實現(xiàn)可能并不一致。

在最初使用Threadlocal的時候幅骄,很容易會產(chǎn)生的誤解就是threadlocal就是一個線程劫窒。

首先來看下Threadlocal的簡單例子:

一個簡單的Person類:

public class Person {

    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

一個簡單的activity:

  public class MainActivity extends Activity {

    //ThreadLocal初始化
    private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();
    private Person mPerson = new Person("王大俠", 100);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //將mPerson對象設(shè)置進去
        mThreadLocal.set(mPerson);
        Log.d("主線程", "  名字:" + mThreadLocal.get().name + "  年齡:" + mThreadLocal.get().age);
        }
    }

運行看看是否能得到mperson對象:

04-19 13:14:31.053 2801-2801/com.example.franky.myapplication d/主線程: 名字:王大俠 年齡:100

果然得到了!說明當前線程確實已經(jīng)存儲了mPerson對象的引用拆座。

那么我們開啟個子線程看看適合還能獲取到mPerson對象呢:

public class MainActivity extends Activity {

//ThreadLocal初始化
private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();
private Person mPerson = new Person("王大俠", 100);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //將mPerson對象設(shè)置進去
    mThreadLocal.set(mPerson);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d("子線程", "  名字:" + mThreadLocal.get().name + "  年齡:" + mThreadLocal.get().age);
        }
    }).start();
}
}

運行看看結(jié)果:

`java.lang.NullPointerException: Attempt to read from field 'java.lang.String com.example.franky.myapplication.Person.name' on a null object reference`

哈哈主巍,報錯信息很明顯,空指針異常挪凑,這清楚的表明子線程是獲取不到mperson對象的孕索,但可能到這里一些朋友可能有些暈了,明明我通過set()方法將mperson設(shè)置給threadlocal對象了啊躏碳,為啥在這里get()不到呢搞旭?

這里我們開始追蹤threadlocal的源碼看看有沒有線索來解釋這個疑問。

首先我們可以看看threadlocal的構(gòu)造方法:

/**
 * Creates a new thread-local variable.
 */
public ThreadLocal() {}

構(gòu)造方法平淡無奇菇绵,那么我們瞅瞅threadlocal的類說明吧肄渗,看看有沒有發(fā)現(xiàn):

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.

個人英文其實不是很好,大致的意思是每個線程都能在自己的線程保持一個對象咬最,如果在一個線程改變對象的屬性不會影響其他線程翎嫡。但我們不要誤讀,如果某個對象是共享變量永乌,那么在某個線程中改變它時钝的,其他線程訪問的時候其實該對象也被改變了翁垂,所以并不是說ThreadLocal是線程安全的,我們只要理解ThreadLocal是能在當前線程保存一個對象的硝桩,這樣我們不用到處傳遞這個對象沿猜。

那ThreadLocal是線程嗎?其實看看threadlocal有沒有繼承thread類就知道了:

    public class ThreadLocal<T> {
}

答案是沒有~~碗脊,這說明threadlocal并不是線程啼肩。

明白了這點,那我們繼續(xù)往下看看ThreadLocal是如何將對象保存起來的衙伶,瞅瞅set()方法:

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

首先通過Thread currentthread = thread.currentthread();獲取到當前線程

然后currentthread作為方法參數(shù)傳遞給了vlaues方法:

 Values values(Thread current) {
    return current.localValues;
}

這里我們看到return的是thread類的一個成員變量祈坠,我們瞅瞅Thread類中的這個變量:

ThreadLocal.Values localValues;

這里我們看到localvalues成員變量的類型就是ThreadLocal.Values
這個類其實是ThreadLocal的內(nèi)部類。

然后這里判斷得到的values對象是不是null矢劲,也就是說Thread類中的成員變量localvalues是不是null赦拘,由于我們是初次設(shè)置,所以這個對象肯定是null芬沉,那繼續(xù)走

values initializevalues(thread current) { return current.localvalues = new values();}

很明顯直接給localvalues變量new了一個value對象躺同。那我們看看values對象里有啥:

首先看看構(gòu)造:

 Values() {
        initializeTable(INITIAL_SIZE);
        this.size = 0;
        this.tombstones = 0;
    }

看起來是初始化了一些成員變量的值,INITIAL_SIZE的值為16,

看看initializeTable(INITIAL_SIZE)這個方法是做啥的:

 private void initializeTable(int capacity) {
        this.table = new Object[capacity * 2];
        this.mask = table.length - 1;
        this.clean = 0;
        this.maximumLoad = capacity * 2 / 3; // 2/3
    }

初始化了長度為32的table數(shù)組丸逸,mask為31蹋艺,clean為0,maximumLoad為10黄刚。

又是一堆成員變量捎谨,那只好看看變量的說明是做啥的:

這個table很簡單就是個object[]類型,意味著可以存放任何對象憔维,變量說明:

    /**
     * Map entries. Contains alternating keys (ThreadLocal) and values.
     * The length is always a power of 2.
     */
    private Object[] table;

疤尉取!原來這里就是存放保存的對象的业扒。
其他的變量再看看:

/** Used to turn hashes into indices. */
    private int mask;

 /** Number of live entries. */
    private int size;

 /** Number of tombstones. */
    private int tombstones;

 /** Maximum number of live entries and tombstones. */
    private int maximumLoad;
 /** Points to the next cell to clean up. */
    private int clean;

這樣看來mask是用來計算數(shù)組下標的检吆,size其實是存活的保存的對象數(shù)量,tombstones是過時的對象數(shù)量凶赁,maximumLoad是最大的保存數(shù)量咧栗,clean是指向的下一個要清理的位置逆甜。大概明白了這些我們再繼續(xù)看:

values.put(this, value);

繼續(xù)追蹤:

/**
     * 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;
            }
        }
    }

該方法直接將this對象和要保存的對象傳遞了進來虱肄,

第一行的cleanUp()其實是用來對table執(zhí)行清理的,比如清理一些過時的對象交煞,檢查是否對象的數(shù)量是否超過設(shè)置值咏窿,或者擴容等,這里不再細說素征,有興趣大家可以研究下集嵌。

然后利用key.hash&mask計算下標萝挤,這里key.hash的初始化值:

private static AtomicInteger hashCounter = new AtomicInteger(0);
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

然后注釋說為了確保計算的下標指向的是key值而不是value,當然為啥用上述數(shù)值進行計算就能保證獲取的key值根欧,貌似是和這個0x61c88647數(shù)值有關(guān)怜珍,再深入的大家可以留言。

然后最重要的就是

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

這里將自身的引用凤粗,而且是弱引用酥泛,放在了table[index]上,將value放在它的下一個位置嫌拣,保證key和value是排列在一起的柔袁,這樣其實我們知道了key其實是threadlocal的引用,值是value异逐,它們一同被放置在table數(shù)組內(nèi)捶索。

所以現(xiàn)在的情況是這樣,Thread類中的成員變量localValuesThreadLocal.Values類型,所以說白了就是當前線程持有了ThreadLocal.Values這樣的數(shù)據(jù)結(jié)構(gòu)灰瞻,我們設(shè)置的value全部都存儲在里面了腥例,當然如果我們在一個線程中new了很多ThreadLocal對象,其實指向都是Thread類中的成員變量localValues箩祥,而且如果new了很多ThreadLocal對象院崇,其實都是放在table中的不同位置的。

那接下來看看get()方法:

  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);
}

代碼比較簡單了袍祖,首先還是獲取當前線程底瓣,然后獲取當前線程的Values對象,也就是Thread類中的成員變量localValues蕉陋,然后拿到Values對象的table數(shù)組捐凭,計算下標,獲取保存的對象凳鬓,當然如果沒有獲取到return (T) values.getAfterMiss(this)茁肠,就是返回null了,其實看方法Object getAfterMiss(ThreadLocal<?> key)中的這個代碼:

Object value = key.initialValue();

    protected T initialValue() {
    return null;
}

就很清楚了缩举,當然我們可以復(fù)寫這個方法來實現(xiàn)自定義返回垦梆,大家有興趣可以試試。

到此我們再回過頭來看看開始的疑問仅孩,為啥mThreadLocal在子線程獲取不到mPerson對象呢托猩?原因就在于子線程獲取自身線程中的localValues變量中并未保存mPerson,真正保存的是主線程辽慕,所以我們是獲取不到的京腥。

看完了ThreadLocal我們再看看它的一個子類InheritableThreadLocal,該類和ThreadLocal最大的不同就是它可以在子線程獲取到保存的對象溅蛉,而ThreadLocal只能在同一個線程公浪,我們看看簡單的例子:
public class MainActivity extends Activity {

private InheritableThreadLocal<Person> mInheritableThreadLocal = new InheritableThreadLocal<Person>();
private Person mPerson = new Person("王大俠", 100);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //將mPerson設(shè)置到當前線程
    mInheritableThreadLocal.set(mPerson);
    Log.d("主線程"+Thread.currentThread().getName(), "  名字:" + mInheritableThreadLocal.get().name + "  年齡:" + mInheritableThreadLocal.get().age);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d("子線程"+Thread.currentThread().getName(), "  名字:" + mInheritableThreadLocal.get().name + "  年齡:" + mInheritableThreadLocal.get().age);
        }
    }).start();
}}

運行看看輸出:

04-21 13:09:11.046 19457-19457/com.example.franky.myapplication D/主線程main:   名字:王大俠  年齡:100
04-21 13:09:11.083 19457-21729/com.example.franky.myapplication D/子線程Thread-184:   名字:王大俠  年齡:100

很明顯在子線程也獲取到了mPerson對象他宛,那它是如何實現(xiàn)的呢?
看下源碼:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

/**
 * Creates a new inheritable thread-local variable.
 */
public InheritableThreadLocal() {
}

/**
 * Computes the initial value of this thread-local variable for the child
 * thread given the parent thread's value. Called from the parent thread when
 * creating a child thread. The default implementation returns the parent
 * thread's value.
 *
 * @param parentValue the value of the variable in the parent thread.
 * @return the initial value of the variable for the child thread.
 */
protected T childValue(T parentValue) {
    return parentValue;
}

@Override
Values values(Thread current) {
    return current.inheritableValues;
}

@Override
Values initializeValues(Thread current) {
    return current.inheritableValues = new Values();
}
}

很明顯InheritableThreadLocal重寫了兩個方法:

Values values(Thread current)方法返回了Thread類中的成員變量inheritableValues欠气。

Values initializeValues(Thread current)也是new的對象也是指向inheritableValues厅各。

ThreadLocal中都是指向的localValues這個變量。

也就是說當我們調(diào)用set(T value)方法時预柒,根據(jù)前面的分析讯检,其實初始化的是這個inheritableValues,那么既然子線程能夠獲取到保存的對象卫旱,那我們看看這個變量在Thread類中哪里有調(diào)用人灼,搜索下就看到:

private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
    ...

    // Transfer over InheritableThreadLocals.
    if (currentThread.inheritableValues != null) {
        inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
    }

    // add ourselves to our ThreadGroup of choice
    this.group.addThread(this);
}

Thread類中的create方法中可以看到,該方法在Thread構(gòu)造方法中被調(diào)用顾翼,如果currentThread.inheritableValues不為空,就會將它傳遞給Values的有參構(gòu)造:

 /**
     * Used for InheritableThreadLocals.
     */
    Values(Values fromParent) {
        this.table = fromParent.table.clone();
        this.mask = fromParent.mask;
        this.size = fromParent.size;
        this.tombstones = fromParent.tombstones;
        this.maximumLoad = fromParent.maximumLoad;
        this.clean = fromParent.clean;
        inheritValues(fromParent);
    }

這里可以看到將inheritableValues的值完全復(fù)制過來了投放,所以我們在子線程一樣可以獲取到保存的變量,我們的分析就到此為止吧适贸。
自己總結(jié)的肯定有很多紕漏灸芳,還請大家多多指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拜姿,一起剝皮案震驚了整個濱河市烙样,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蕊肥,老刑警劉巖谒获,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壁却,居然都是意外死亡批狱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門展东,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赔硫,“玉大人,你說我怎么就攤上這事盐肃∽Σ玻” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵砸王,是天一觀的道長推盛。 經(jīng)常有香客問我,道長处硬,這世上最難降的妖魔是什么小槐? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任拇派,我火速辦了婚禮荷辕,結(jié)果婚禮上凿跳,老公的妹妹穿的比我還像新娘。我一直安慰自己疮方,他們只是感情好控嗜,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骡显,像睡著了一般疆栏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惫谤,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天壁顶,我揣著相機與錄音,去河邊找鬼溜歪。 笑死若专,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蝴猪。 我是一名探鬼主播调衰,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼自阱!你這毒婦竟也來了嚎莉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沛豌,失蹤者是張志新(化名)和其女友劉穎趋箩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體加派,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡阁簸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哼丈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片启妹。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖醉旦,靈堂內(nèi)的尸體忽然破棺而出饶米,到底是詐尸還是另有隱情,我是刑警寧澤车胡,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布檬输,位于F島的核電站,受9級特大地震影響匈棘,放射性物質(zhì)發(fā)生泄漏丧慈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逃默。 院中可真熱鬧鹃愤,春花似錦、人聲如沸完域。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吟税。三九已至冯痢,卻和暖如春它匕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工寸癌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留红竭,地道東北人狮腿。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓杨名,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泽艘。 傳聞我的和親對象是個殘疾皇子欲险,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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