【多執行緒】ThreadLocal原理

post200發表於2021-09-09

使用

    ThreadLocal threadLocalA= new ThreadLocal();
    threadLocalA.set(new String("A"));    String str = threadLocalA.get();

原理

在每個執行緒的內部有個資料結構為Map的threadLocals變數,以的形式儲存著執行緒變數和其對應的值。

當使用set()方法時:

  1. 獲取到當前執行緒的threadLocals,型別為Map

  2. 將這值放到這個Map結構的變數中,key為ThreadLocal物件,value為所有存放的值

當使用get()方法時:

  1. 獲取到當前執行緒的threadLocals,型別為Map。

  2. 以ThreadLocal物件為Map的key獲取到它的value值。

因為ThreadLocal物件作為Map的key,所以一個ThreadLocal物件只能存放一個值,當存放多個時,會將新值覆蓋舊值。

原始碼

資料結構:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//當前執行緒為入參,獲取當前執行緒的threadLocals變數
        if (map != null)         //入參為this,也就是說key為ThreadLocal物件
            map.set(this, value);        else
            createMap(t, value);
    }    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//當前執行緒為入參,獲取當前執行緒的threadLocals
        if (map != null) {            //入參為this,也就是說key為ThreadLocal
            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;                return result;
            }
        }        return setInitialValue();
    }    
    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;//threadLocals為執行緒的變數
    }    
    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);//避免記憶體洩漏,下文有提。
    }

記憶體洩漏

ThreadLocalMap結構如下:

static class ThreadLocalMap {        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference> {            
            Object value;

            Entry(ThreadLocal> k, Object v) {                super(k); //key,這個是一個弱引用,如果沒有強引用來引用key(也就是ThreadLocal),則key會被回收,形成了key為null的Entry。
                value = v;
            }
        }

        private Entry[] table;
}

threadLocals變數是線上程內部的,故沒有多個執行緒去訪問它,所以不存線上程不安全的說法,同時只要執行緒被回收了就不會存在記憶體洩漏。

ThreadLocal物件被回收時(key為null),沒有辦法獲取到value,而執行緒又不會被回收時則value一直佔用空間導致記憶體洩漏。執行緒不會被回收的常見場景是執行緒池。

JDK在此做了一個最佳化,在呼叫get(),set(),remove()方法會做額外處理來清理ThreadLocalMap中key為null的value,以減少記憶體洩漏的影響。但是如果key未使用弱引用,即使ThreadLocal被回收了,key也不為null,也就是說是沒法判斷哪個value需要回收的,最終造成記憶體洩漏。所以此處的弱引用key是記憶體洩漏的一個最佳化處理方式。

使用場景

在實際專案中,可以用來減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度,因為servlet是單例多執行緒的,每個請求執行的操作都是同一個執行緒中。比如:可以用ThreadLocal來存每一次請求使用者的資訊,定義了一個類UserContextHolder

public class UserContextHolder{
    private static ThreadLocal userContextHolder = new ThreadLocal();    public static setUser(User user){
        userContextHolder.set(user);
    }    public static User getUser(){        return userContextHolder.get();
    }    public static void remove(){        return userContextHolder.remove();
    }
}

當使用者每次請求進來時,在攔截器中獲取使用者資訊呼叫UserContextHolder.setUser()將其放到userContext中,無論在哪我們只要呼叫UserContextHolder.getUser()可以很輕鬆的獲取到使用者的資訊,而不用在函式呼叫時一層一層的傳遞。同時在攔截器結束時呼叫UserContextHolder.remove()移除掉即可。


作者:eejron
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2249/viewspace-2802539/,如需轉載,請註明出處,否則將追究法律責任。

相關文章