1. 程式人生 > >分析ThreadLocal的實現原理

分析ThreadLocal的實現原理

ThreadLocal是開發中常會使用的一個工具,從類的名字就可以看出,它為執行緒提供本地變數,即:每個執行緒私有的資料。下面直接進入原始碼。 1、使用方法:

// Entity 存放執行緒要儲存的資訊
ThreadLocal<Entity> threadLocal = new ThreadLocal<Entity>();
//為執行緒設定私有資料
threadLocal.set(new Entity());
//拿出執行緒私有資料
threadLocal.get();

2、為執行緒設定私有資料: ①ThreadLocal的set()方法

  public void set(T value)
{ Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //該執行緒的threadLocals已經初始化過,則呼叫ThreadLocalMap的set()方法,將資料push進來,否則呼叫createMap()函式初始化該變數 if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap
(Thread t) { //返回該執行緒的threadLocals變數 return t.threadLocals; } void createMap(Thread t, T firstValue) { //初始化該執行緒的threadLocals變數 t.threadLocals = new ThreadLocalMap(this, firstValue); } public class Thread implements Runnable { //每一個執行緒會有唯一的一個ThreadLocalMap型別變數,用來儲存該執行緒所有的私有資料
ThreadLocal.ThreadLocalMap threadLocals = null; }

ThreadLocalMapset()方法,從上面知道每一個執行緒有個ThreadLocalMap的例項,用來儲存該執行緒的私有資料(一個執行緒可能會存很多不同型別的私有資料)。

//ThreadLocalMap 構造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
			//資料是儲存在一個數組裡面的,初始長度為16
            table = new Entry[INITIAL_CAPACITY];
            //計算資料放在陣列的哪個位置,是根據ThreadLocal 的threadLocalHashCode值來計算的
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //以ThreadLocal例項為key,放入實際的資料
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        
//Entry類實際儲存著資料,繼承 WeakReference       
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                //儲存放進來的執行緒私有資料
                value = v;
            }
        }

//ThreadLocalMap set方法
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //計算資料放入的位置
            int i = key.threadLocalHashCode & (len-1);
			//檢測hash衝突,如果該位置已經放入了一個數據(e!=null),則線性地向後查詢第一個合適的位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //Entry 繼承自WeakReference,所以用get()方法,可以直接拿到需要的key(ThreadLocal的例項)
                ThreadLocal<?> k = e.get();
				//找到一個位置,k和當前要插入的key值相同,更新該位置的資料
                if (k == key) {
                    e.value = value;
                    return;
                }
				//找到一個位置已經有資料,但是key為null,則替換掉廢棄的key
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//將key-value儲存到Entry中,放在陣列的i位置
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //判斷是否需要擴充套件陣列,這裡是長度大於陣列長度的2/3時擴充套件陣列
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

以上就將某個資料儲存到執行緒私有資料中了,總結一下:Thread中有個ThreadLocalMap例項,ThreadLocalMap中有個陣列:Entry[] table;Entry中實際儲存了資料value。也就是說一個執行緒可以有n多個私有資料,存放在ThreadLocalMap中,每一個私有資料對應一個ThreadLocal例項和一個Entry例項。

3、取出資料: ①ThreadLocal的get()方法:

    public T get() {
        Thread t = Thread.currentThread();
        //還是首先要拿到ThreadLocalMap例項
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//以自己為key,從map中取出實際儲存的資料,上面說過:資料是以ThreadLocal為key,實際資料為value儲存到Entry物件中的
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMapgetEntry()方法:

 private Entry getEntry(ThreadLocal<?> key) {
 			//計算陣列下標,和放入資料時一樣
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //當前位置有資料,且key和要需要的一致,說明該位置儲存了要的資料
            if (e != null && e.get() == key)
                return e;
            else
            	//雜湊衝突,根據需要的key線性向後查詢
                return getEntryAfterMiss(key, i, e);
        }

以上就是ThreadLocal儲存執行緒私有資料的大致實現原理,瞭解這些,相信對以後使用ThreadLocal會有積極的幫助。