溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Netty的FastThreadLocal速度快的原因是什么

發(fā)布時間:2021-06-29 10:43:49 來源:億速云 閱讀:133 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要講解了“Netty的FastThreadLocal速度快的原因是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Netty的FastThreadLocal速度快的原因是什么”吧!

前言

最近在看netty源碼的時候發(fā)現(xiàn)了一個叫FastThreadLocal的類,jdk本身自帶了ThreadLocal類,所以可以大致想到此類比jdk自帶的類速度更快,主要快在什么地方,以及為什么速度更快,下面做一個簡單的分析;

性能測試

ThreadLocal主要被用在多線程環(huán)境下,方便的獲取當前線程的數(shù)據(jù),使用者無需關心多線程問題,方便使用;為了能說明問題,分別對兩個場景進行測試,分別是:多個線程操作同一個ThreadLocal,單線程下的多個ThreadLocal,下面分別測試:

1.多個線程操作同一個ThreadLocal

分別對ThreadLocal和FastThreadLocal使用測試代碼,部分代碼如下:

public static void test2() throws Exception {
        CountDownLatch cdl = new CountDownLatch(10000);
        ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        long starTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    threadLocal.set(Thread.currentThread().getName());
                    for (int k = 0; k < 100000; k++) {
                        threadLocal.get();
                    }
                    cdl.countDown();
                }
            }, "Thread" + (i + 1)).start();
        }
        cdl.await();
        System.out.println(System.currentTimeMillis() - starTime + "ms");
    }

以上代碼創(chuàng)建了10000個線程,同時往ThreadLocal設置,然后get十萬次,然后通過CountDownLatch來計算總的時間消耗,運行結果為:1000ms左右;
下面再對FastThreadLocal進行測試,代碼類似:

public static void test2() throws Exception {
        CountDownLatch cdl = new CountDownLatch(10000);
        FastThreadLocal<String> threadLocal = new FastThreadLocal<String>();
        long starTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            new FastThreadLocalThread(new Runnable() {

                @Override
                public void run() {
                    threadLocal.set(Thread.currentThread().getName());
                    for (int k = 0; k < 100000; k++) {
                        threadLocal.get();
                    }
                    cdl.countDown();
                }
            }, "Thread" + (i + 1)).start();
        }

        cdl.await();
        System.out.println(System.currentTimeMillis() - starTime);
    }

運行之后結果為:1000ms左右;可以發(fā)現(xiàn)在這種情況下兩種類型的ThreadLocal在性能上并沒有什么差距,下面對第二種情況進行測試;

2.單線程下的多個ThreadLocal

分別對ThreadLocal和FastThreadLocal使用測試代碼,部分代碼如下:

    public static void test1() throws InterruptedException {
        int size = 10000;
        ThreadLocal<String> tls[] = new ThreadLocal[size];
        for (int i = 0; i < size; i++) {
            tls[i] = new ThreadLocal<String>();
        }
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                long starTime = System.currentTimeMillis();
                for (int i = 0; i < size; i++) {
                    tls[i].set("value" + i);
                }
                for (int i = 0; i < size; i++) {
                    for (int k = 0; k < 100000; k++) {
                        tls[i].get();
                    }
                }
                System.out.println(System.currentTimeMillis() - starTime + "ms");
            }
        }).start();
    }

以上代碼創(chuàng)建了10000個ThreadLocal,然后使用同一個線程對ThreadLocal設值,同時get十萬次,運行結果:2000ms左右;
下面再對FastThreadLocal進行測試,代碼類似:

    public static void test1() {
        int size = 10000;
        FastThreadLocal<String> tls[] = new FastThreadLocal[size];
        for (int i = 0; i < size; i++) {
            tls[i] = new FastThreadLocal<String>();
        }
        
        new FastThreadLocalThread(new Runnable() {

            @Override
            public void run() {
                long starTime = System.currentTimeMillis();
                for (int i = 0; i < size; i++) {
                    tls[i].set("value" + i);
                }
                for (int i = 0; i < size; i++) {
                    for (int k = 0; k < 100000; k++) {
                        tls[i].get();
                    }
                }
                System.out.println(System.currentTimeMillis() - starTime + "ms");
            }
        }).start();
    }

運行結果:30ms左右;可以發(fā)現(xiàn)性能達到兩個數(shù)量級的差距,當然這是在大量訪問次數(shù)的情況下才有的效果;下面重點分析一下ThreadLocal的機制,以及FastThreadLocal為什么比ThreadLocal更快;

ThreadLocal的機制

因為我們常用的就是set和get方法,分別看一下對應的源碼:

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

以上代碼大致意思:首先獲取當前線程,然后獲取當前線程中存儲的threadLocals變量,此變量其實就是ThreadLocalMap,最后看此ThreadLocalMap是否為空,為空就創(chuàng)建一個新的Map,不為空則以當前的ThreadLocal為key,存儲當前value;可以進一步看一下ThreadLocalMap中的set方法:

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

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

大致意思:ThreadLocalMap內部使用一個數(shù)組來保存數(shù)據(jù),類似HashMap;每個ThreadLocal在初始化的時候會分配一個threadLocalHashCode,然后和數(shù)組的長度進行取模操作,所以就會出現(xiàn)hash沖突的情況,在HashMap中處理沖突是使用數(shù)組+鏈表的方式,而在ThreadLocalMap中,可以看到直接使用nextIndex,進行遍歷操作,明顯性能更差;下面再看一下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();
    }

同樣是先獲取當前線程,然后獲取當前線程中的ThreadLocalMap,然后以當前的ThreadLocal為key,到ThreadLocalMap中獲取value:

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

同set方式,通過取模獲取數(shù)組下標,如果沒有沖突直接返回數(shù)據(jù),否則同樣出現(xiàn)遍歷的情況;所以通過分析可以大致知道以下幾個問題:
1.ThreadLocalMap是存放在Thread下面的,ThreadLocal作為key,所以多個線程操作同一個ThreadLocal其實就是在每個線程的ThreadLocalMap中插入的一條記錄,不存在任何沖突問題;
2.ThreadLocalMap在解決沖突時,通過遍歷的方式,非常影響性能;
3.FastThreadLocal通過其他方式解決沖突的問題,達到性能的優(yōu)化;
下面繼續(xù)來看一下FastThreadLocal是通過何種方式達到性能的優(yōu)化。

為什么Netty的FastThreadLocal速度快

Netty中分別提供了FastThreadLocal和FastThreadLocalThread兩個類,F(xiàn)astThreadLocalThread繼承于Thread,下面同樣對常用的set和get方法來進行源碼分析:

   public final void set(V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            set(InternalThreadLocalMap.get(), value);
        } else {
            remove();
        }
    }

    public final void set(InternalThreadLocalMap threadLocalMap, V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            if (threadLocalMap.setIndexedVariable(index, value)) {
                addToVariablesToRemove(threadLocalMap, this);
            }
        } else {
            remove(threadLocalMap);
        }
    }

此處首先對value進行判定是否為InternalThreadLocalMap.UNSET,然后同樣使用了一個InternalThreadLocalMap用來存放數(shù)據(jù):

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

可以發(fā)現(xiàn)InternalThreadLocalMap同樣存放在FastThreadLocalThread中,不同在于,不是使用ThreadLocal對應的hash值取模獲取位置,而是直接使用FastThreadLocal的index屬性,index在實例化時被初始化:

    private final int index;

    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }

再進入nextVariableIndex方法中:

    static final AtomicInteger nextIndex = new AtomicInteger();
     
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

在InternalThreadLocalMap中存在一個靜態(tài)的nextIndex對象,用來生成數(shù)組下標,因為是靜態(tài)的,所以每個FastThreadLocal生成的index是連續(xù)的,再看一下InternalThreadLocalMap中是如何setIndexedVariable的:

    public boolean setIndexedVariable(int index, Object value) {
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object oldValue = lookup[index];
            lookup[index] = value;
            return oldValue == UNSET;
        } else {
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

indexedVariables是一個對象數(shù)組,用來存放value;直接使用index作為數(shù)組下標進行存放;如果index大于數(shù)組長度,進行擴容;get方法直接通過FastThreadLocal中的index進行快速讀取:

   public final V get(InternalThreadLocalMap threadLocalMap) {
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        return initialize(threadLocalMap);
    }
    
    public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length? lookup[index] : UNSET;
    }

直接通過下標進行讀取,速度非常快;但是這樣會有一個問題,可能會造成空間的浪費;

總結

通過以上分析我們可以知道在有大量的ThreadLocal進行讀寫操作的時候,才可能會遇到性能問題;另外FastThreadLocal通過空間換取時間的方式來達到O(1)讀取數(shù)據(jù);還有一個疑問就是內部為什么不直接使用HashMap(數(shù)組+黑紅樹)來代替ThreadLocalMap。

感謝各位的閱讀,以上就是“Netty的FastThreadLocal速度快的原因是什么”的內容了,經(jīng)過本文的學習后,相信大家對Netty的FastThreadLocal速度快的原因是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI