在Spring MVC 单条请求 之间共享数据
业务:已登录用户,访问controller,我们需要获得它的user数据;
解决思路
1.request 域
2.session域
--->需要在Controler 方法的参数上传入request,代码过于侵入式;
3.THreadLocal--->好
实现:1.拦截器中,获取cookie-token
2.根据token,获取到redis中的user数据
3.存在就放行,并将user数据写入threadLocal中;
4.在拦截器的afterCompletion()方法里写上释放ThreadLocal对象的方法:因为GC无法回收ThreadLocal,会造成内存泄漏,从而长期以往造成内存溢出;
为了解决线程安全的问题,我们有3个思路:
第一每个线程独享自己的操作对象,也就是多例,多例势必会带来堆内存占用、频繁GC、对象初始化性能开销等待等一些列问题。
第二单例模式枷锁,典型的案例是HashTable和Vector,对读取和变更的操作用synchronized限制起来,保证同一时间只有一个线程可以操作该对象。虽然解决了内存、回收、构造、初始化等问题,但是势必会因为锁竞争带来高并发下性能的下降。
第三个思路就是今天重点推出的ThreadLocal。单例模式下通过某种机制维护成员变量不同线程的版本。
先来看Thread:
Thread中维护了一个ThreadLocal.ThreadLocalMapthreadLocals = null; ThreadLocalMap这个Map的key是ThreadLocal,value是维护的成员变量。现在的跟踪链是Thread->ThreadLocalMap-><ThreadLocal,Object>,那么我们只要搞明白Thread怎么跟ThreadLocal关联的,从线程里找到自己关心的成员变量的快照这条线就通了。
- 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);
- }
再来看ThreadLocal:它里面核心方法两个get()和set(T)
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- "unchecked")(
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
- }
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
方法里通过Thread.currentThread()的方法得到当前线程,然后做为key存储到当前线程对象的threadLocals中,也就是TreadLocalMap中。
OK,这样整个关系链已经建立,真正要去访问的成员变量在一个map中,key是线程号,值是属于该线程的快照。
ThreadLocal里还有map的创建createMap(t, value)、取值时对象的初始值setInitialValue()、线程结束时对象的释放remove()等细节,有兴趣的可以继续跟进了解下。
ThreadLocal应用其实很多,例如Spring容器中实例默认是单例的,transactionManager也一样,那么事务在处理时单例的manager是如何控制每个线程的事务要如何处理呢,这里面就应用了大量的ThreadLocal。
工具类
/**
* ThreadLocal 工具类
* 用来在单线程之间共享数据
* 用完记得关哦,不然会内存泄漏
* @author luo
*
*/
public class UserThreadLocal {
/**
* 1.定义ThreadLocal
* 使用规则:
* 1.如果有单个数据需要使用ThreadLocal进行数据的传输,则泛型就是单个对象的类型.
* 2.如果有多个数据需要使用ThreadLocal进行数据的传输,那么用Map
*/
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public static User getUser(){
return threadLocal.get();
}
public static void setUser(User user){
threadLocal.set(user);
}
//切记用完要remove!!
public static void remove(){
threadLocal.remove();
}