三. 线程管理之ThreadLocal
不忘初心 砥礪前行, Tomorrow Is Another Day !
相關文章
- 一. 線程管理之Thread基礎
- 二. 線程管理之線程池
- 三. 線程管理之ThreadLocal
- 四. 線程管理之Android中的多線程
本文概要:
在Android系統源碼中,多處用到了Threadlocal,如最熟悉的Handler中的Looper,其次還有屬性動畫中AnimationHandler、ActivityThread、AMS都有涉及到.接下來一起來認識與了解它.
一. 認識ThreadLocal
概念:線程內部的數據存儲類,可以實現在不同線程具有不同數據副本.它的作用域僅限于當前當前線程,線程之間互不干擾.
基本使用
這里定義了一個ThreadLocal用來存儲Integer類型的數據,設置了默認值為999.分別在MainThread,SubThreadA對ThreadLocal的值進行設置,SubThreadB未進行設置值.
示例源碼
public class ThreadLocalActivity extends AppCompatActivity {private static final String TAG = "ThreadLocalActivity";private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return 999;}};public static void startActivity(Context pkgContext) {Intent intent = new Intent(pkgContext, ThreadLocalActivity.class);pkgContext.startActivity(intent);}@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_threadlocal);threadLocal.set(100);new SubThreadA("SubThreadA").start();new SubThreadB("SubThreadB").start();System.out.println("MainThread:" + threadLocal.get());}class SubThreadA extends Thread {public SubThreadA(String name) {super(name);}@Overridepublic void run() {threadLocal.set(110);int value = threadLocal.get();System.out.println("SubThreadA:" + value);}}class SubThreadB extends Thread {public SubThreadB(String name) {super(name);}@Overridepublic void run() {int value = threadLocal.get();System.out.println("SubThreadB:" + value);}}}//調用輸出 I: SubThreadA:110 I: MainThread:100 I: SubThreadB:999復制代碼最后通過調用輸出結果,可以清晰的看到雖然操作的是同一個ThreadLocal對象,但是在三個不同線程存儲的值是互不影響的.回過頭再對照看概念性內容就清晰明了許多.另外在SubThreadB因為沒有設置值,所以獲得的是我們初始化設置的默認值999.
二. 了解ThreadLocal的實現原理
認識了它基本使用,接著一起來了解ThreadLocal的基本原理,之所以用了解,因為筆者自己對ThreadLocal的理解有限且實際開發中運用較少.這里就簡單的看下原理.
2.1 當調用Set方法進行存儲值時.
對應源碼
public void set(T value) {Thread t = Thread.currentThread();//獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)//不為空,直接存儲值map.set(this, value);else//為空,創建一個ThreadLocalMap并存儲值.createMap(t, value);} 復制代碼從上面源碼可以看出,我們設置的值,是存儲在ThreadLocalMap下的.當第一次存儲時,先會createMap初始化一個ThreadLocalMap.接著看如何進行初始化的.
對應源碼
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); }ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//將新值存儲在table數組中,該數組類型是Entry,擴展了弱引用封裝了ThreadLocal與value.table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY); } 復制代碼通過以上源碼,可以很清楚的發現最終我們設置的值,是存儲在一個Entry類型的table數組中的,這個Entry封裝了ThreadLocal與Value.
接著,如果不是第一次set值,那么會進入ThreadLocalMap的set方法,我們繼續往下看.
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {//替換存在的值e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}//將值存儲在table數組中tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();} 復制代碼這里不分析具體算法,從上面可以發現最終也是保存在table數組中.
2.2 當調用Get方法獲取存儲值時.
對應源碼
public T get() {Thread t = Thread.currentThread();//同樣獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//不為空,獲取對應值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//為空,返回設置的默認值return setInitialValue(); } 復制代碼通過以上源碼,get方法簡單來說就是從當前線程的ThreadLocalMap對象中獲取存儲的值.
通過SET與GET源碼簡單分析,我們知道它們所操作的對象都是當前線程的ThreadLolocalMap對象的table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,它們對ThreadLocal所做的讀寫操作僅限于各自線程的內部.
本文小結
最后我們對ThreadLocal的實現流程做一個小結.
- 該table數組是Entry類型,封裝了ThreadLocal與Value.
最后奉上一個極簡版的ThreadLocal的工作流程圖.
ThreadLocal工作流程極簡圖
由于本人技術有限,如有錯誤的地方,麻煩大家給我提出來,本人不勝感激,大家一起學習進步.
參考鏈接:
- www.cnblogs.com/whoislcj/p/…
總結
以上是生活随笔為你收集整理的三. 线程管理之ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用注解装配Bean
- 下一篇: (新聞) 2008 Altera 亞洲創