ThreadLocal原始碼分析與實踐
阿新 • • 發佈:2020-09-23
ThreadLocal是什麼?
ThreadLocal是一個執行緒內部儲存類,提供執行緒內部儲存功能,在一個ThreadLocal物件中,每一個執行緒都儲存各自獨立的資料,互不干擾
示例如下:
public class ThreadLocalTest { @Test public void test() throws InterruptedException { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); Thread thread1 = new Thread(newMyTask(threadLocal, 10)); Thread thread2 = new Thread(new MyTask(threadLocal, 100)); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } } class MyTask implements Runnable { private ThreadLocal<Integer> threadLocal; private intvalue; public MyTask(ThreadLocal<Integer> threadLocal, int value) { this.threadLocal = threadLocal; this.value = value; } @Override public void run() { threadLocal.set(++value); System.out.println(threadLocal.get()); } }
原始碼分析
get()方法
publicT get() { // ThreadLocalMap是ThreadLocal中的一個類內部類,而每一個Thread例項都擁有一個ThreadLocalMap例項變數用來儲存執行緒的內部資料 Thread t = Thread.currentThread(); // 獲取執行緒例項變數ThreadLocalMap ThreadLocalMap map = getMap(t); // 如果map!=null則表示Thread中的ThreadLocalMap之前已經例項過 if (map != null) { // ThreadLocalMap例項中有陣列例項Entry[] table用於儲存真正的資料,key為ThreadLocal,value為儲存的值,所以一個執行緒可以同時維護多個ThreadLocal ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } }
// 初始化ThreadLocalMap例項 return setInitialValue(); }
// getMap就是獲取當前執行緒下的ThreadLocalMap例項 ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
// 根據ThreadLocal獲取對應的value private Entry getEntry(ThreadLocal<?> key) {
// 內部儲存為陣列形式,通過就算key的hashCode進而確認索引位置 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } // 如果map為null,則初始化ThreadLocalMap private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
真實資料是儲存在Thread物件的ThreadLocalMap例項中,所以每個執行緒都維護自己的內部資料,當有多個ThreadLocal時,每個ThreadLocal根據hashCode匹配到一個索引儲存
set()方法
publicvoidset(T value)
Thread t=Thread.currentThread();
ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length;
// 計算索引值 int i = key.threadLocalHashCode & (len-1); // ThreadLocalMap使用線性探測法來解決雜湊衝突,假設計算後的i為10,該位置k不為null且與key不相等,則匹配索引為11的位置,一直重複下去直到可以插入為止
// 當然這裡不會出現走了一個迴圈還沒有空位置可以插入 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; } } // 查到為空的位置插入資料 tab[i] = new Entry(key, value); int sz = ++size;
// 判斷是否需要擴容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
應用例項:
當使用spring框架支援資料庫事務時,需要將獲取的資料庫連線與當前執行緒繫結在一起,這時應用的就是ThreadLocal儲存執行緒內部資料的特性,多次操作資料庫使用的都是同個連線,這樣才能保證事務的完成。