java源碼學習---ThreadLocal

ThreadLocal 是一個線程安全副本,用於儲存僅容許當前線程能訪問/修改的值,不知從什麼時候起看到了」線程安全「這種字眼就會不自覺想到性能問題,可是ThreadLocal是實現線程安全的另一種方案"空間換時間"。數組

先看2個小Demo安全

使用ThreadLocalide

public class ThreadLoacalTest{

    // 定義線程安全副本
    private final static ThreadLocal<Integer> THREAD_NUMBER = new ThreadLocal<Integer>();

    // 繼承Thread重寫run()
    static class ThreadTest extends Thread{
        @Override
        public void run() {
            for (int i=0; i < 3 ;i++){
                // 若是THREAD_NUMBER null,賦值0,不然+1
                THREAD_NUMBER.set(THREAD_NUMBER.get() == null ? 0 : THREAD_NUMBER.get() + 1);
                // 打印信息
                System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER.get());
            }
            THREAD_NUMBER.remove();
        }
    }

    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        ThreadTest t2 = new ThreadTest();
        ThreadTest t3 = new ThreadTest();
        t1.start();
        t2.start();
        t3.start();
    }
}

不使用ThreadLocal性能

public class Test {

    // 定義普一般量
    public static Integer THREAD_NUMBER = 0;

    // 繼承Thread重寫run()
    static class ThreadTest extends Thread{
        @Override
        public void run() {
            for (int i=0; i < 3 ;i++){
                // 打印信息
                System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER++);
            }
        }
    }

    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        ThreadTest t2 = new ThreadTest();
        ThreadTest t3 = new ThreadTest();
        t1.start();
        t2.start();
        t3.start();
    }
}

從上面的打印結果能夠看出,使用了ThreadLocal的常量不會與其餘線程共享,而沒有使用ThreadLocal的常量是會與其餘線程共享學習

接下來看源碼this

從set()開始吧spa

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

1.先獲取當前線程對象線程

2.經過當前線程對象獲取一個ThreadLocalMap 實例(如下簡稱map)3d

3.若是map不爲null 則直接向map插入數據對象

4.若是爲null則調用createMap()建立一個map而且向map插入數據

該map以當前對象做爲key,這樣咱們就只須要關注value而不用維護key

getMap()

能夠看到這個threadLocals屬性是Thread的,可是類是屬於ThreadLocal的一個內部類ThreadLocalMap

由於這個map對象屬於Thread的實例,每一個Thread都是特有的map,因此能提供整個線程使用且不與其餘線程共享

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;

createMap()

2個參數,一個是key(當前ThreadLocal實例),一個是value(咱們維護的對象)

下面代碼能夠看出值都是保存在一個Entry對象的數組裏面,以及一些初始化的工做

Entry又是ThreadLocal.ThreadLocalMap 的一個內部類 ThreadLocal.ThreadLocalMap.Entry

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

ThreadLocalMap.set()

在這裏向Entry[]添加元素,計算出下標,若是該下標位置沒有元素,則直接插入元素。若是有元素,則遍歷數組直到沒有元素的下標位置才停下進行存儲,若是遇到相同key則更新元素而且遇到key爲null的Entry時,會刪除元素。存儲完以後判斷當前數組容量是否須要擴容,若是進行了擴容,全部元素都會從新存儲一遍

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;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

接下來看看get()

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap 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();
}

咱們獲取ThreadLocal維護的變量都是直接經過ThreadLocal的get()獲取

1.獲取當前線程對象

2.經過當前線程對象獲取map

3.經過當前對象向map獲取值(Entry對象),若是map與Entry對象都不爲null則直接返回存儲的值

4.若是值爲null,則調用setInitialValue()初始化鍵值對並返回null

setInitialValue()

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;
}

setInitialValue() 先經過initialValue()初始化值,而後保存到map而且返回初始化的值

(判斷是否須要建立map這個步驟實在不想寫了,ThreadLocal裏面太多這樣的操做了)

protected T initialValue() {
    return null;
}

ThreadLocalMap.getEntry()

private Entry getEntry(ThreadLocal<?> key) {
    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);
}

首先是計算出key對應值儲存的下標,當元素不爲null且key相等,則返回對應的值,若是下標與key不對應,則遍歷數組查詢,整個數組都不存在該key,則返回null,若是遇到key爲null的Entry時,會刪除元素

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

以上源碼學習總結:

1.ThreadLocal原理是該類主要操做一個Thread類的 ThreadLocalMap threadLocals屬性,該屬性是屬於每一個Thread實例特有的,因此能提供整個線程使用且不與其餘線程共享

2.ThreadLocalMap 的底層實際上是一個素組,當計算到的存儲下標已存在元素,則循環判斷是否爲null(可否存儲),若是存在相同key則更新元素

3.get()、set()操做的時候若是碰到了key爲null的狀況都會刪除key爲null的 Entry 對象

都說ThreadLocal是以"空間換時間",以上代碼也證明了確實如此。可是有個問題,就是空間用得越多,就越容易OOM。因此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;
    }
}

這裏的key是弱引用,就是key指向的對象(ThreadLocal)若是沒有其餘強引用的狀況下,下次GC的時候就會被回收,這保證了必定的垃圾回收效率,可是若是存在其餘強引用狀況下,GC並不會回收該對象,因此咱們使用ThreadLocal的時候須要注意有沒有被強引用

相關文章
相關標籤/搜索