从获取用户信息来看ThreadLocal做了些什么

总要有一个梦想或大或小

共 7610字,需浏览 16分钟

 · 2021-07-21

系统登陆后后台service是怎么获取用户信息的呢,说实话,我也不知道,最近在做项目时遇到这么一个问题,就认真思考了下,在SpringMVC中找到了答案,就写下来记录下吧


在项目中有个WebUtil工具,这里面是有获取用户信息的方法的,但我是要找到他是怎么保存并获取这个用户信息的,当然不能只看的这么浅显


这个工具类中有个方法是获取request请求的,我很好奇,获取请求,它是怎么做到的,系统每秒少则几千,多则上万的请求并发,它获取的请求到底是哪个请求,是我想要的那个吗?

 public static HttpServletRequest getRequest() {    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();    return requestAttributes == null ? null :((ServletRequestAttributes)requestAttributes).getRequest();  }

跟着就可以看到一个请求上下文容器RequestContextHolder,getRequestAttributes获取了请求属性,这个请求属性不重要,反正不是我想要看到的,就看看这个容器里有啥吧

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 */@Nullablepublic static RequestAttributes getRequestAttributes() {RequestAttributes attributes = requestAttributesHolder.get();if (attributes == null) {attributes = inheritableRequestAttributesHolder.get();}return attributes;}

代码里有个稍微熟悉一点的类了,很熟悉但不完全熟悉。NamedThreadLocal和NamedInheritableThreadLocal

我以前也不知道这两个类是啥,我只知道ThreadLocal。

先看下ThreadLocal是啥吧

ThreadLocal看名字指的是线程本地的变量,也就是说由ThreadLocal保存的变量是属于当前线程的,这个变量对于其他变量是隔离的,是不透明的,ThreaLocal为每一个线程都保存了一份这个变量的副本,那么每个线程就可以访问自己线程的副本变量了,怎么感觉是在说废话呢?

来看下源码是怎么写的吧,源码中主要的两个方法就是set方法和get方法

public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }
public T get() {   //获取当前线程       Thread t = Thread.currentThread();   //从当前线程中获取线程变量ThreadLocalMap       ThreadLocalMap map = getMap(t);       if (map != null) {           //从map中根据key(也就是当前ThreadLocal对象)获取其对应的Entry节点           ThreadLocalMap.Entry e = map.getEntry(this);           if (e != null) {               @SuppressWarnings("unchecked")               T result = (T)e.value;               return result;          }      }   //初始化当前线程的ThredLocalMap或者当前ThreadLocal对应的属性值   //这个方法和set()方法类似,但是这个方法的默认value值是null       return setInitialValue();}
/***set()方法和setInitialValue()类似,但是set()方法是去设置ThreadLocal对应的值*如果当前线程的ThreadLocalMap为null,将会先创建map再设置第一个值*/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和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; else return 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的静态内部类,而Entry是ThreadLocalMap的静态内部类(禁止套娃警告)


ThreadLocalMap的作用就是为了保存多个ThreadLocal变量,以ThreadLocal对象为key,属性值为value


从上面源码可以看到ThreadLocalMap中真正保存持有这些ThreadLocal变量的对象是一个Entry数组对象


看着ThreadLocal风风火火,其实它实际的用处就是hash出一个index值,这个index表示ThreadLocal属性在Entry数组中的位置

但是如果hash值一样的话,那不就产生hash冲突了吗?它是这样做的

采用线性探测的方法一个一个的探测当前位置是否存在Entry对象,如果有,就去比对ThreadLocal对象是否与Entry中取出的key一致。


如果一致就替换当前key对应的属性值;如果不一样,则调用replaceStaleEntry来设置值,同时会去清除那些key为空的value,以避免产生内存泄漏问题,也会将该Entry置为null,以备下次被使用

Entry中的键使用WeakReference修饰的,当ThreadLocal不再被使用时,将会及时被回收,但是Entry中value是强引用,这样的话Entry仍然会一直存在于内存中,及时该Entry对象已经形同虚设,所以Java8中对此作了优化,在ThreadLocal的get()、set()、remove()调用时,会去清空那些key为空但是value不为空的Entry对象,避免发生内存泄漏问题


每个ThreadLocal只能保存一个变量副本,如果想要一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。

ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。


Thread还持有一个inheritableThreadLocals引用,有兴趣的同学可以自行研究下



浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报