ThreadLocal源碼分析

1、簡介java

  1.   咱們知道多個線程同時讀寫同一共享變量存在併發問題,爲此咱們能夠突破共享變量,沒有共享變量就不會有併發問題,可使用局部變量。正所謂沒有共享,就沒有傷害,本質上就是避免共享,除了局部變量,Java語言提供的線程本地存儲(ThreadLocal)就能作到。


       其實每一個線程內部都會維護一個ThreadLocalMap屬性,每份線程獨自的數據都存放在ThreadLocalMap中Entry[] table屬性裏,Entry對象的key就是ThreadLocalvalue就是本身設置的值,若是程序裏有多個ThreadLoca屬性,每一個線程在運行時會將用到ThreadLocal,生成Entry保存到table中,有點須要注意的是同一個Entry中value從新設置會被替換,如Entry<ThreadLocal,value>中value被設置成value2,變成Entry<ThreadLocal,value2>,和Map相似,若是想保存多個值,能夠將value封裝成對象。數組

2、屬性bash

//ThreadLocal中的hash值,目的是爲了定位存放在ThreadLocalMap中Entry[] table的那個位置
private final int threadLocalHashCode = nextHashCode();
//原子類,對hashCode進行累加HASH_INCREMENT
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;複製代碼

3、內部類併發

一、SuppliedThreadLocalide

//目的是爲了在初始化調用ThreadLocal.get()方法時,能根據傳入的函數式接口supplier的get方法獲取默認值
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {    
    private final Supplier<? extends T> supplier;    
    SuppliedThreadLocal(Supplier<? extends T> supplier) 
    {        
        this.supplier = Objects.requireNonNull(supplier);    
    }   
     @Override 
    protected T initialValue() {        
    return supplier.get();    
    }
}複製代碼

二、ThreadLocalMap函數

//每一個線程Thread,內部都維護了一個ThreadLocal.ThreadLocalMap threadLocals = null屬性,每一個線程的//數據都各自保存在各自的ThreadLocalMap的table屬性中,table中的每一個元素都是Entry<ThreadLocal,Value>,
//每一個ThreadLocal,都會做爲Entry的key,好比有個Test類,內部有兩個ThreadLocal,若是A線程在運行時,使用到兩個ThreadLocal,那線程A中的ThreadLocalMap中的table[Entry0<ThreadLocal,value>,Entry1<ThreadLocal1,value1>]
static class ThreadLocalMap {    
    static class Entry extends WeakReference<ThreadLocal<?>> {        
        Object value;        
        Entry(ThreadLocal<?> k, Object v) {            
           super(k);            
           value = v;        
       }    
   }
    //table初始容量 
     private static final int INITIAL_CAPACITY = 16;
    //保存每一個線程中的使用到的ThreadLocal做爲key的多個Entry 
    private Entry[] table;    
    //table中的元素
    private int size = 0;
    //擴容的閾值 
    private int threshold; 複製代碼

4、構造ThreadLocal對象ui

  1. 靜態方法withInitial構造

    //傳入函數式接口,構造ThreadLocal的靜態內部類子類SuppliedThreadLocal,ThreadLocal.get()還未設置值時獲取null,SuppliedThreadLocal.get()獲取的值是傳入的函數式接口supplier的get方法的值
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {    
         return new SuppliedThreadLocal<>(supplier);
    }複製代碼

  2. 構造函數構造

    public ThreadLocal() {}複製代碼

5、get方法this

public T get() {
    //獲取當前執行的線程 
    Thread t = Thread.currentThread();
    //從Thread中獲取threadLocals屬性,即Thread中的ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap map = getMap(t);//看下面的getMap方法
    //若是當前線程的ThreadLocalMap不爲null
    if (map != null) {
        //getEntry看下面介紹的ThreadLocalMap的getEntry方法 
        ThreadLocalMap.Entry e = map.getEntry(this);        
        if (e != null) {            
            @SuppressWarnings("unchecked")            
            T result = (T)e.value;            
            return result;        
       }    
    }    
    return setInitialValue();}複製代碼

getMap方法,從Thread中獲取threadLocal屬性spa

ThreadLocalMap getMap(Thread t) {    
    return t.threadLocals;
}複製代碼

setInitialValue方法,初次構造ThreadLocalMap對象,並設置firstValue線程

private T setInitialValue() {
    //若是是經過構造函數構造ThreadLocal,initialValue()方法返回null,若是是經過withInitial靜態方法構造ThreadLocal,initialValue()方法返回supplier.get();
    T value = initialValue();
    //獲取當前線程 
    Thread t = Thread.currentThread();
    //從Thread中獲取threadLocal屬性 
    ThreadLocalMap map = getMap(t);    
    if (map != null)
        //若是map不爲null,將其value設置進去,key爲當前的ThreadLocal,set會在下面set方法中介紹 
        map.set(this, value);   
     else
        //構造ThreadLocalMap,而且將其ThreadLocalMap賦值給當前線程的threadLocals屬性 
        createMap(t, value);//t.threadLocals = new ThreadLocalMap(this, firstValue);
    return value;}複製代碼

ThreadLocalMap構造方法

  1. 公共的構造方法

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                //建立個大小爲16的Entry數組
                table = new Entry[INITIAL_CAPACITY];
                //根據ThreadLocal的threadLocalHashCode屬性值&上table數組長度-1,計算出該ThreadLocal的Entry的存放位置
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                //將其Entry存入數組中,Entry繼承於弱應用,這樣的話當ThreadLocal被賦值爲null,gc能夠回收掉ThreadLocal,而不至於Entry對ThreadLocal的引用,致使gc回收不了,防止內存泄漏 
                table[i] = new Entry(firstKey, firstValue);    
                //table數組中存在的元素
                size = 1;
                //設置擴容的閾值大小 threshold = len * 2 / 3 table數組長度乘於2除以3 
                setThreshold(INITIAL_CAPACITY);
    }複製代碼

  2. 私有的構造方法,

    //線程間的InheritableThreadLocal傳遞,這個會在詳細介紹InheritableThreadLocal中介紹
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        //父線程的ThreadLocalMap 
        Entry[] parentTable = parentMap.table;
        //長度 
        int len = parentTable.length;
        //設置當前線程的閾值 
        setThreshold(len);
        //建立大小和父線程相同的Entry數組 
        table = new Entry[len];
        //將其父線程中的ThreadLocalMap中Entry和Entry的key(ThreadLocal)不爲空的Entry的元素添加到子線程的Entry的table中 
        for (int j = 0; j < len; j++) {        
            Entry e = parentTable[j];        
            if (e != null) {            
                @SuppressWarnings("unchecked")            
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();            
                if (key != null) {
                    //childValue()方法只有在ThreadLocalMap的子類InheritableThreadLocal中才能使用,不然在ThreadLocal中調用會報UnsupportedOperationException異常
                    Object value = key.childValue(e.value);//return parentValue
                    Entry c = new Entry(key, value);                
                    //計算Entry的存放位置
                    int h = key.threadLocalHashCode & (len - 1);                
                    //若是當前位置有存在,則存放下一個位置,只到下一個位置沒有元素
                    while (table[h] != null)                    
                        h = nextIndex(h, len);                
                    table[h] = c;
                    //當前線程的Entry的table數組的元素大小加1 
                    size++;            
                }        
            }    
        }
    }複製代碼

       createInheritedMap方法,調用ThreadLocalMap的私有構造方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {    
    return new ThreadLocalMap(parentMap);
}複製代碼

      createInheritedMap方法的在哪裏被調用

//Thread類中的init方法
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
    //.....省略n行代碼,inheritThreadLocals默認爲true,若是建立當前線程的父線程inheritableThreadLocal不爲空,將其inheritableThreadLocals的ThreadLocalMap中的Entry的table數組賦值給當前線程 //好比在main線程中建立了test線程,而且main中的屬性inheritableThreadLocals不爲空,會將其inheritableThreadLocals中的的ThreadLocalMap中的Entry的table數組賦值給test線程,inheritableThreadLocals屬性的類只能是InheritableThreadLocal
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);       
}複製代碼

ThreadLocalMap的getEntry方法

private Entry getEntry(ThreadLocal<?> key) {
    //ThreadLocal的threadLocalHashCode & 上 ThreadLocalMap的Entry數組table的長度-1,定位元素所在的下標 
    int i = key.threadLocalHashCode & (table.length - 1);    
    Entry e = table[i];
    //entry不等於null而且e.get和當前的ThreadLocal相等,返回entry 
    if (e != null && e.get() == key)        
        return e;    
    else
        //若是entry等於null,獲取e.get()和當前的ThreadLocal不相等 
        return getEntryAfterMiss(key, i, e);}複製代碼

ThreadLocalMap的getEntryAfterMiss方法

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    //ThreadLocalMap的Entry數組table 
    Entry[] tab = table;
    //數組長度 
    int len = tab.length;
    //若是Entry e爲null,直接返回空,e不爲空,找到key和當前ThreadLocal相等的Entry,不然只到遍歷完table中的size 
    while (e != null) {
        //獲取從entry中獲取key 
        ThreadLocal<?> k = e.get();
        //若是key和當前的ThreadLocal相等,則直接返回entry 
        if (k == key)            
            return e;
        //若是當前的entry的key爲null,即外部將其ThreadLocal的屬性賦值爲null,獲取被gc回收 
        if (k == null) 
            //清除當前key爲null的Entry,而且從新計算後面的每一個Entry元素的存放位置,說白了就是全部元素的位置都向前移動 
            expungeStaleEntry(i);        
        else 
            //獲取下一個ThreadLocal爲key的Entry 
            i = nextIndex(i, len);        
        e = tab[i];    
    }    
    return null;
}複製代碼

ThreadLocalMap的expungeStaleEntry方法

private int expungeStaleEntry(int staleSlot) {    //當前線程的ThreadLocalMap的Entry數組table屬性
    Entry[] tab = table;
    //table數組的長度 
    int len = tab.length;
    //將其當前key爲null的entry的value也設置爲null,加快value的gc回收 
     tab[staleSlot].value = null;
    //將其整個entry設置爲null 
     tab[staleSlot] = null;
    //table數組中存在的元素減1 
     size--;    
     Entry e;    
     int i;
    //從當前key爲null的Entry的存放位置statleSlot位置開始日後面開始找到key爲null的entry而且清除掉,不然的話,從新計算key不爲null的Entry元素的存放位置 
      for (i = nextIndex(staleSlot, len); (e = tab[i]) != null;i = nextIndex(i, len)) {
          ThreadLocal<?> k = e.get();
          //當前位置的entry的key爲null,清除value和entry,而且table數組元素減1 
          if (k == null) {            
             e.value = null;            
             tab[i] = null;            
             size--;        
          } else {
            //從新計算當前位置的entry的存放位置 
            int h = k.threadLocalHashCode & (len - 1);            
            if (h != i) {
                //將之前位置的entry置爲null 
                tab[i] = null;
                //從新找到新的存放位置 
                while (tab[h] != null)                    
                   h = nextIndex(h, len);                
                tab[h] = e;            
            }        
         }    
     }
    //返回當前操做過的i 
    return i;
}


複製代碼

6、set方法

public void set(T value) {
    //獲取當前線程 
    Thread t = Thread.currentThread();
    //從當前線程中獲取ThreadLocalMap 
    ThreadLocalMap map = getMap(t);    
    if (map != null)        
        map.set(this, value);    
    else
        //若是ThreadLocalMap不存在,構造ThreadLocalMap,而且將其ThreadLocalMap賦值給當前線程的threadLocals屬性 ,ThreadLocalMap的構造函數能夠看上面的介紹
        createMap(t, value);//t.threadLocals = new ThreadLocalMap(this, firstValue); 
}複製代碼

ThreadLocalMap的set(this,value)方法

private void set(ThreadLocal<?> key, Object value) {    
    Entry[] tab = table;    
    int len = tab.length;    
    int i = key.threadLocalHashCode & (len-1);
    //從當前算出來的位置i從後開始遍歷 
    for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) {        
        ThreadLocal<?> k = e.get();
        //尋找到k和當前的ThreadLocal相等,替換值,爲此一個ThreadLocal只能存放線程的一個值 
        if (k == key) {
            //將當前值設置到value中 
            e.value = value;            
            return;        
        }
        //若是在遍歷的過程當中,存在某一項key爲null的Entry 
        if (k == null) {            
            //替換元素,而且作一些清除和從新計算元素的存放位置的操做,能夠看下面replaceStaleEntry方法的介紹
            replaceStaleEntry(key, value, i);            
            return;        
        }    
    }
    //當前位置i上本來就沒元素,或者在上面的遍歷中,沒有找到相同的key,或者某一項key的值爲null的entry替換,而是從遍歷中找到位置上沒有元素的i 
    tab[i] = new Entry(key, value);
    //table的元素加1 
    int sz = ++size;
    //若是沒有清除一些entry(key爲null),而且ThreadLocalMap中的元素個數大於閾值,則對table進行擴容 
    if (!cleanSomeSlots(i, sz) && sz >= threshold)        
    rehash();
}

複製代碼

replaceStaleEntry方法

private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {    
    Entry[] tab = table;    
    int len = tab.length;    
    Entry e;
    //將key爲null的entry的位置staleSlot賦值給新的變量 
    int slotToExpunge = staleSlot;
    //從slotToExpunge位置向前遍歷,找到比此值更前的key爲null的entry的在table中的下標 
    for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))
        //若是在遍歷前面table元素的時候找到key爲null的entry,對slotToExpunge的值進行從新賦值 
        if (e.get() == null)            
            slotToExpunge = i;
    //從當前的staleSlot向後遍歷 
    for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {        
        ThreadLocal<?> k = e.get();
        //若是後面找到和當前ThreadLocal相等key的Entry 
        if (k == key) {
            //賦予新值 
            e.value = value;
            將參數傳進來的staleSlot(key爲null的entry下標)賦值給當前ThreadLocal的Entry的所在位置,說白了就是兩個互換位置            
            tab[i] = tab[staleSlot];            
            tab[staleSlot] = e;
            //若是前面沒有找到比當前staleSlot更前的key爲null的Entry位置,將當前i賦值給slotToExpunge 
             if (slotToExpunge == staleSlot)                
                 slotToExpunge = i;
            //cleanSomeSlots方法能夠看下面的cleanSomeSlots介紹,expungeStaleEntry方法能夠看上面expungeStaleEntry方法的介紹 
            cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);            return;        }
        //找到後面第一項key爲null的entry的下標i,賦值給slotToExpunge,目的是爲了後面執行expungeStaleEntry方法,清除key爲null的entry,而且從新移動後面元素的存儲位置 
        if (k == null && slotToExpunge == staleSlot)            
            slotToExpunge = i;    
    }
    //將其當前傳進來key爲null的entry的value置空 
    tab[staleSlot].value = null;
    //從新建立Entry對象存入當前位置 
    tab[staleSlot] = new Entry(key, value);
    //若是slotToExpunge和staleSlot不相等,說明上面有找到其餘key爲null的entry 
    if (slotToExpunge != staleSlot)
        //cleanSomeSlots和expungeStaleEntry配合使用,expungeStaleEntry清除後面key爲null的entry和對key不爲null的entry從新向前移動存儲位置,cleanSomeSlots是爲了清除前半部分key爲null的entry數據 
        cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);}複製代碼

cleanSomeSlots方法

private boolean cleanSomeSlots(int i, int n) {    
    boolean removed = false;    
    Entry[] tab = table;    
    int len = tab.length;    
    do { 
        //獲取table中的下一個元素 
        i = nextIndex(i, len);        
        Entry e = tab[i];
        //若是當前entry不爲null,key爲null 
        if (e != null && e.get() == null) {            
            n = len;            
            removed = true;
            //調用上面介紹的expungeStaleEntry方法,尋找後面的key爲null的entry,將值和entry置爲空,而且對後面key不爲null的entry從新賦值到新位置 
            i = expungeStaleEntry(i);        
        }    
    } while ( (n >>>= 1) != 0);    
    return removed;
}複製代碼


7、remove方法

public void remove() {
    //從當前線程中獲取ThreadLocalMap 
    ThreadLocalMap m = getMap(Thread.currentThread());
    //若是當前的ThreadLocalMap不爲null,調用下面的那個remove方法,說明若是當前線程沒有設置ThreadLocal.ThreadLocalMap threadLocals = null屬性,調用remove沒影響
    if (m != null)        
        m.remove(this);
}

private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //從當前位置開始日後找,找到key和當前ThreadLocal相等的Entry
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    //Entry元素的key設置爲null
                    e.clear();
                    //從當前位置開始查詢key爲null的entry,和從新計算後面存在的entry元素的位置,看上面expungeStaleEntry方法的介紹
                    expungeStaleEntry(i);
                    return;
                }
            }
        }複製代碼

有不清楚的地方,能夠留言一塊兒探討

相關文章
相關標籤/搜索