Android 詳解ThreadLocal及InheritableThreadLocal
Android 詳解ThreadLocal及InheritableThreadLocal
概要:
因?yàn)樵赼ndroid中經(jīng)常用到handler來處理異步任務(wù),通常用于接收消息,來操作UIThread,其中提到涉及到的looper對(duì)象就是保存在Threadlocal中的,因此研究下Threadlocal的源碼。
分析都是基于android sdk 23 源碼進(jìn)行的,ThreadLocal在android和jdk中的實(shí)現(xiàn)可能并不一致。
在最初使用Threadlocal的時(shí)候,很容易會(huì)產(chǎn)生的誤解就是threadlocal就是一個(gè)線程。
首先來看下Threadlocal的簡單例子:
一個(gè)簡單的Person類:
public class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } }
一個(gè)簡單的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對(duì)象設(shè)置進(jìn)去 mThreadLocal.set(mPerson); Log.d("主線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age); } }
運(yùn)行看看是否能得到mperson對(duì)象:
04-19 13:14:31.053 2801-2801/com.example.franky.myapplication d/主線程: 名字:王大俠 年齡:100
果然得到了!說明當(dāng)前線程確實(shí)已經(jīng)存儲(chǔ)了mPerson對(duì)象的引用。
那么我們開啟個(gè)子線程看看適合還能獲取到mPerson對(duì)象呢:
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對(duì)象設(shè)置進(jìn)去 mThreadLocal.set(mPerson); new Thread(new Runnable() { @Override public void run() { Log.d("子線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age); } }).start(); } }
運(yùn)行看看結(jié)果:
`java.lang.NullPointerException: Attempt to read from field ' java.lang.String com.example.franky.myapplication.Person.name' on a null object reference`
哈哈,報(bào)錯(cuò)信息很明顯,空指針異常,這清楚的表明子線程是獲取不到mperson對(duì)象的,但可能到這里一些朋友可能有些暈了,明明我通過set()方法將mperson設(shè)置給threadlocal對(duì)象了啊,為啥在這里get()不到呢?
這里我們開始追蹤threadlocal的源碼看看有沒有線索來解釋這個(gè)疑問。
首先我們可以看看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.
個(gè)人英文其實(shí)不是很好,大致的意思是每個(gè)線程都能在自己的線程保持一個(gè)對(duì)象,如果在一個(gè)線程改變對(duì)象的屬性不會(huì)影響其他線程。但我們不要誤讀,如果某個(gè)對(duì)象是共享變量,那么在某個(gè)線程中改變它時(shí),其他線程訪問的時(shí)候其實(shí)該對(duì)象也被改變了,所以并不是說ThreadLocal是線程安全的,我們只要理解ThreadLocal是能在當(dāng)前線程保存一個(gè)對(duì)象的,這樣我們不用到處傳遞這個(gè)對(duì)象。
那ThreadLocal是線程嗎?其實(shí)看看threadlocal有沒有繼承thread類就知道了:
public class ThreadLocal<T> { }
答案是沒有~~,這說明threadlocal并不是線程。
明白了這點(diǎn),那我們繼續(xù)往下看看ThreadLocal是如何將對(duì)象保存起來的,瞅瞅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();獲取到當(dāng)前線程
然后currentthread作為方法參數(shù)傳遞給了vlaues方法:
Values values(Thread current) { return current.localValues; }
這里我們看到return的是thread類的一個(gè)成員變量,我們瞅瞅Thread類中的這個(gè)變量:
ThreadLocal.Values localValues;
這里我們看到localvalues成員變量的類型就是ThreadLocal.Values
這個(gè)類其實(shí)是ThreadLocal的內(nèi)部類。
然后這里判斷得到的values對(duì)象是不是null,也就是說Thread類中的成員變量localvalues是不是null,由于我們是初次設(shè)置,所以這個(gè)對(duì)象肯定是null,那繼續(xù)走
values initializevalues(thread current) { return current.localvalues = new values();}
很明顯直接給localvalues變量new了一個(gè)value對(duì)象。那我們看看values對(duì)象里有啥:
首先看看構(gòu)造:
Values() { initializeTable(INITIAL_SIZE); this.size = 0; this.tombstones = 0; }
看起來是初始化了一些成員變量的值,INITIAL_SIZE的值為16,
看看initializeTable(INITIAL_SIZE)這個(gè)方法是做啥的:
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。
又是一堆成員變量,那只好看看變量的說明是做啥的:
這個(gè)table很簡單就是個(gè)object[]類型,意味著可以存放任何對(duì)象,變量說明:
/** * Map entries. Contains alternating keys (ThreadLocal) and values. * The length is always a power of 2. */ private Object[] table;
啊!原來這里就是存放保存的對(duì)象的。
其他的變量再看看:
/** 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是用來計(jì)算數(shù)組下標(biāo)的,size其實(shí)是存活的保存的對(duì)象數(shù)量,tombstones是過時(shí)的對(duì)象數(shù)量,maximumLoad是最大的保存數(shù)量,clean是指向的下一個(gè)要清理的位置。大概明白了這些我們?cè)倮^續(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對(duì)象和要保存的對(duì)象傳遞了進(jìn)來,
第一行的cleanUp()其實(shí)是用來對(duì)table執(zhí)行清理的,比如清理一些過時(shí)的對(duì)象,檢查是否對(duì)象的數(shù)量是否超過設(shè)置值,或者擴(kuò)容等,這里不再細(xì)說,有興趣大家可以研究下。
然后利用key.hash&mask計(jì)算下標(biāo),這里key.hash的初始化值:
private static AtomicInteger hashCounter = new AtomicInteger(0); private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
然后注釋說為了確保計(jì)算的下標(biāo)指向的是key值而不是value,當(dāng)然為啥用上述數(shù)值進(jìn)行計(jì)算就能保證獲取的key值,貌似是和這個(gè)0x61c88647數(shù)值有關(guān),再深入的大家可以留言。
然后最重要的就是
if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; }
這里將自身的引用,而且是弱引用,放在了table[index]上,將value放在它的下一個(gè)位置,保證key和value是排列在一起的,這樣其實(shí)我們知道了key其實(shí)是threadlocal的引用,值是value,它們一同被放置在table數(shù)組內(nèi)。
所以現(xiàn)在的情況是這樣,Thread類中的成員變量localValues是ThreadLocal.Values類型,所以說白了就是當(dāng)前線程持有了ThreadLocal.Values這樣的數(shù)據(jù)結(jié)構(gòu),我們?cè)O(shè)置的value全部都存儲(chǔ)在里面了,當(dāng)然如果我們?cè)谝粋€(gè)線程中new了很多ThreadLocal對(duì)象,其實(shí)指向都是Thread類中的成員變量localValues,而且如果new了很多ThreadLocal對(duì)象,其實(shí)都是放在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); }
代碼比較簡單了,首先還是獲取當(dāng)前線程,然后獲取當(dāng)前線程的Values對(duì)象,也就是Thread類中的成員變量localValues,然后拿到Values對(duì)象的table數(shù)組,計(jì)算下標(biāo),獲取保存的對(duì)象,當(dāng)然如果沒有獲取到return (T) values.getAfterMiss(this),就是返回null了,其實(shí)看方法Object getAfterMiss(ThreadLocal<?> key)中的這個(gè)代碼:
Object value = key.initialValue(); protected T initialValue() { return null; }
就很清楚了,當(dāng)然我們可以復(fù)寫這個(gè)方法來實(shí)現(xiàn)自定義返回,大家有興趣可以試試。
到此我們?cè)倩剡^頭來看看開始的疑問,為啥mThreadLocal在子線程獲取不到mPerson對(duì)象呢?原因就在于子線程獲取自身線程中的localValues變量中并未保存mPerson,真正保存的是主線程,所以我們是獲取不到的。
看完了ThreadLocal我們?cè)倏纯此囊粋€(gè)子類InheritableThreadLocal,該類和ThreadLocal最大的不同就是它可以在子線程獲取到保存的對(duì)象,而ThreadLocal只能在同一個(gè)線程,我們看看簡單的例子:
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è)置到當(dāng)前線程 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(); }}
運(yùn)行看看輸出:
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對(duì)象,那它是如何實(shí)現(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重寫了兩個(gè)方法:
Values values(Thread current)方法返回了Thread類中的成員變量inheritableValues。
Values initializeValues(Thread current)也是new的對(duì)象也是指向inheritableValues。
而ThreadLocal中都是指向的localValues這個(gè)變量。
也就是說當(dāng)我們調(diào)用set(T value)方法時(shí),根據(jù)前面的分析,其實(shí)初始化的是這個(gè)inheritableValues,那么既然子線程能夠獲取到保存的對(duì)象,那我們看看這個(gè)變量在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不為空,就會(huì)將它傳遞給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ù)制過來了,所以我們?cè)谧泳€程一樣可以獲取到保存的變量,我們的分析就到此為止吧。
自己總結(jié)的肯定有很多紕漏,還請(qǐng)大家多多指正。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Android 自定義可拖拽View界面渲染刷新后不會(huì)自動(dòng)回到起始位置
這篇文章主要介紹了Android 自定義可拖拽View界面渲染刷新后不會(huì)自動(dòng)回到起始位置的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02Android 自定義Button控件實(shí)現(xiàn)按鈕點(diǎn)擊變色
這篇文章給大家介紹了android 自定義Button控件實(shí)現(xiàn)按鈕點(diǎn)擊變色的代碼,本文給大家附有注釋,非常不錯(cuò),代碼簡單易懂,對(duì)android按鈕點(diǎn)擊變色的實(shí)現(xiàn)感興趣的朋友參考下吧2016-11-11Android 實(shí)現(xiàn)列表倒計(jì)時(shí)功能
這篇文章主要介紹了Android 實(shí)現(xiàn)列表倒計(jì)時(shí)功能,代碼很簡單,沒有任何難度,使用RecyclerView+BaseRecyclerViewAdapterHelper列表實(shí)現(xiàn),需要的朋友可以參考下2020-03-03Flutter實(shí)現(xiàn)抖音點(diǎn)贊效果
抖音的點(diǎn)贊效果在第一次看到的時(shí)候,總有一種眼前一亮的感覺。一邊看視頻,還能在視頻上點(diǎn)贊,而且整個(gè)屏幕都能夠點(diǎn)贊,并伴隨動(dòng)畫,還是很炫酷的。今天我們用Flutter來實(shí)現(xiàn)一下這個(gè)效果2021-05-05Android InputMethodManager輸入法簡介
這篇文章主要介紹了Android InputMethodManager輸入法框架的使用,具有參考價(jià)值,需要的朋友可以參考下。2016-06-06Android自定義帶水滴的進(jìn)度條樣式(帶漸變色效果)
這篇文章主要介紹了Android自定義帶水滴的進(jìn)度條樣式(帶漸變色效果)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12Android根據(jù)輸入銀行卡號(hào)判斷屬于哪個(gè)銀行
這篇文章主要介紹了Android根據(jù)輸入銀行卡號(hào)判斷屬于哪個(gè)銀行的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02Android中RecyclerView拖拽、側(cè)刪功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中RecyclerView拖拽、側(cè)刪功能的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-09-09Android里實(shí)現(xiàn)退出主程序的提示代碼
當(dāng)用戶選擇"確定",就退出當(dāng)前的對(duì)話框。其中,有個(gè)很重要的函數(shù),Activity.finish(),通過調(diào)用這個(gè)函數(shù),退出當(dāng)前運(yùn)行的整個(gè)Android程序2013-06-06