1. 程式人生 > 實用技巧 >ThreadLocal原始碼分析與實踐

ThreadLocal原始碼分析與實踐

ThreadLocal是什麼?

ThreadLocal是一個執行緒內部儲存類,提供執行緒內部儲存功能,在一個ThreadLocal物件中,每一個執行緒都儲存各自獨立的資料,互不干擾

示例如下:

public class ThreadLocalTest {

    @Test
    public void test() throws InterruptedException {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        Thread thread1 = new Thread(new
MyTask(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 int
value; 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()方法

  public
T 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儲存執行緒內部資料的特性,多次操作資料庫使用的都是同個連線,這樣才能保證事務的完成。