從獲取用戶信息來(lái)看ThreadLocal做了些什么
系統(tǒng)登陸后后臺(tái)service是怎么獲取用戶信息的呢,說(shuō)實(shí)話,我也不知道,最近在做項(xiàng)目時(shí)遇到這么一個(gè)問(wèn)題,就認(rèn)真思考了下,在SpringMVC中找到了答案,就寫下來(lái)記錄下吧
在項(xiàng)目中有個(gè)WebUtil工具,這里面是有獲取用戶信息的方法的,但我是要找到他是怎么保存并獲取這個(gè)用戶信息的,當(dāng)然不能只看的這么淺顯
這個(gè)工具類中有個(gè)方法是獲取request請(qǐng)求的,我很好奇,獲取請(qǐng)求,它是怎么做到的,系統(tǒng)每秒少則幾千,多則上萬(wàn)的請(qǐng)求并發(fā),它獲取的請(qǐng)求到底是哪個(gè)請(qǐng)求,是我想要的那個(gè)嗎?
public static HttpServletRequest getRequest() {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();return requestAttributes == null ? null :((ServletRequestAttributes)requestAttributes).getRequest();}
跟著就可以看到一個(gè)請(qǐng)求上下文容器RequestContextHolder,getRequestAttributes獲取了請(qǐng)求屬性,這個(gè)請(qǐng)求屬性不重要,反正不是我想要看到的,就看看這個(gè)容器里有啥吧
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes");private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context");/*** Return the RequestAttributes currently bound to the thread.* @return the RequestAttributes currently bound to the thread,* or {@code null} if none bound*/public static RequestAttributes getRequestAttributes() {RequestAttributes attributes = requestAttributesHolder.get();if (attributes == null) {attributes = inheritableRequestAttributesHolder.get();}return attributes;}
代碼里有個(gè)稍微熟悉一點(diǎn)的類了,很熟悉但不完全熟悉。NamedThreadLocal和NamedInheritableThreadLocal
我以前也不知道這兩個(gè)類是啥,我只知道ThreadLocal。
先看下ThreadLocal是啥吧
ThreadLocal看名字指的是線程本地的變量,也就是說(shuō)由ThreadLocal保存的變量是屬于當(dāng)前線程的,這個(gè)變量對(duì)于其他變量是隔離的,是不透明的,ThreaLocal為每一個(gè)線程都保存了一份這個(gè)變量的副本,那么每個(gè)線程就可以訪問(wèn)自己線程的副本變量了,怎么感覺(jué)是在說(shuō)廢話呢?來(lái)看下源碼是怎么寫的吧,源碼中主要的兩個(gè)方法就是set方法和get方法
public class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;}
public T get() {//獲取當(dāng)前線程Thread t = Thread.currentThread();//從當(dāng)前線程中獲取線程變量ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//從map中根據(jù)key(也就是當(dāng)前ThreadLocal對(duì)象)獲取其對(duì)應(yīng)的Entry節(jié)點(diǎn)ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//初始化當(dāng)前線程的ThredLocalMap或者當(dāng)前ThreadLocal對(duì)應(yīng)的屬性值//這個(gè)方法和set()方法類似,但是這個(gè)方法的默認(rèn)value值是nullreturn setInitialValue();}/***set()方法和setInitialValue()類似,但是set()方法是去設(shè)置ThreadLocal對(duì)應(yīng)的值*如果當(dāng)前線程的ThreadLocalMap為null,將會(huì)先創(chuàng)建map再設(shè)置第一個(gè)值*/public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
我們?cè)賮?lái)看下上面所說(shuō)的ThreadLocalMap和Entry是啥吧
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<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/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);}/*** Get the entry associated with key. This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss. This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param key the thread local object* @return the entry associated with key, or null if no such*/private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}/*** Set the value associated with key.** @param key the thread local object* @param value the value to be 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();}/*** Remove the entry for key.*/private void remove(ThreadLocal<?> key) {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)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}}
ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類,而Entry是ThreadLocalMap的靜態(tài)內(nèi)部類(禁止套娃警告)
ThreadLocalMap的作用就是為了保存多個(gè)ThreadLocal變量,以ThreadLocal對(duì)象為key,屬性值為value
從上面源碼可以看到ThreadLocalMap中真正保存持有這些ThreadLocal變量的對(duì)象是一個(gè)Entry數(shù)組對(duì)象
看著ThreadLocal風(fēng)風(fēng)火火,其實(shí)它實(shí)際的用處就是hash出一個(gè)index值,這個(gè)index表示ThreadLocal屬性在Entry數(shù)組中的位置
但是如果hash值一樣的話,那不就產(chǎn)生hash沖突了嗎?它是這樣做的

采用線性探測(cè)的方法一個(gè)一個(gè)的探測(cè)當(dāng)前位置是否存在Entry對(duì)象,如果有,就去比對(duì)ThreadLocal對(duì)象是否與Entry中取出的key一致。
如果一致就替換當(dāng)前key對(duì)應(yīng)的屬性值;如果不一樣,則調(diào)用replaceStaleEntry來(lái)設(shè)置值,同時(shí)會(huì)去清除那些key為空的value,以避免產(chǎn)生內(nèi)存泄漏問(wèn)題,也會(huì)將該Entry置為null,以備下次被使用

Entry中的鍵使用WeakReference修飾的,當(dāng)ThreadLocal不再被使用時(shí),將會(huì)及時(shí)被回收,但是Entry中value是強(qiáng)引用,這樣的話Entry仍然會(huì)一直存在于內(nèi)存中,及時(shí)該Entry對(duì)象已經(jīng)形同虛設(shè),所以Java8中對(duì)此作了優(yōu)化,在ThreadLocal的get()、set()、remove()調(diào)用時(shí),會(huì)去清空那些key為空但是value不為空的Entry對(duì)象,避免發(fā)生內(nèi)存泄漏問(wèn)題
每個(gè)ThreadLocal只能保存一個(gè)變量副本,如果想要一個(gè)線程能夠保存多個(gè)副本以上,就需要?jiǎng)?chuàng)建多個(gè)ThreadLocal。
ThreadLocal內(nèi)部的ThreadLocalMap鍵為弱引用,會(huì)有內(nèi)存泄漏的風(fēng)險(xiǎn)。
每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。
Thread還持有一個(gè)inheritableThreadLocals引用,有興趣的同學(xué)可以自行研究下
