ThrealLocal的通俗含义:将数据存到线程中的本地变量中(数据与线程绑定) 且通过静态方法拿到当前线程从而拿到本地变量,然后拿到数据,实现了线程隔离; 相当于每个线程拥有自己独立的数据空间,可以在里面存取数据,利用这个机制可以实现跨组件的通信,单个线程内的数据共享(不是多线程数据共享) ThrealLocal的简单源码 【1】set部分 【2】get部分 【3】ThreadLocalMap数据结构 ThrealLocal的探索起源:记得当初学习ThreadLocal的时候,仅仅只知道这个静态类可以实现线程安全,但对实际应用完全不知道什么场景下会用到这个技术;后来在一次偶然的调试中,在业务开发中我们经常遇到利用shiro的工具类拿到自己的session,然后拿到用户信息;我产生了一个疑问,session不是应该利用sessionId才能拿出自己的session吗,我没有传sessionId进去,这是知道拿到的session一定是自己的session呢? 后来我进入shiro的源码一看,最后看到了这个会话session是通过ThreadLocal拿出来的,这一样以来我就完全了解了ThreadLocal的作用了,首先shiro会有一个拦截器取出当前request的sessionId,然后利用这个sessionId拿到原本存在内存中的session(再在了MemorySessionDAO里面有个map,用sessionId作为key取出),然后再将这个session,利用ThreadLocal的set方法将这个session跟线程绑定,意味着在线程中调用ThreadLocal的get方法就可以拿出这个session,很显然ThreadLocal的方法是静态的,他能首先能识别当前线程是哪个线程,然后再在线程的对应区域中创建数据也可以从对应区域中取出数据,这就是ThreadLocal的作用,当然这只是通俗的描述,如果想了解ThreadLocal详细的原理和标准分析可以看其他博客,本文只根据应用场景做展示,原理只是略过; ThrealLocal的应用模仿: 依据这个思想,写一个拦截器取出request的数据,然后根据标识id取出session,我们也可以这样做,我的这个案例是来源于我们是springcloud微服务架构,所以业务层没有引入shiro,我想实现在业务层调用一个静态方法可以拿到用户数据; 【1】先封装两个个实体类 【2】写一个拦截器(应用关键) 【3】取出数据 以上代码就是对应我们写的LocalProvider的这个方法,实际就是替代我们操作ThreadLocal的外包装饰,因为这样子写代码就更明确一点,java原生的东西实现的更细更底层更抽象,但是应用上我们可以根据场景将他语义化更明确; ThrealLocal的其他应用(跨组件通信) 对于框架来讲,当你的封装越来越多的时候,显然你想要传一个数据到某一个对象里,这是很难的,难道逐层传参,去研究框架的源码?这显然非常费力而且会造成不可预料的东西,那么ThreadLocal就派上用场了,当然这是某些场景下,比如我的shiro中因为想改造让shiro的sessionId由我生成的Token替代,且这个Token是经过jwt对实体类数据加密后生成的,我想把这个id放到shiro的框架里面并取出来作为id,这就很难实现了,利用ThreadLocal实现就非常简单,因为能保证jwt生成后再去执行session创建工作 【2】因此把sessionId从ThreadLocal中拿出 那么我们就利用这个本地变量线程共享的机制实现了跨组件通信,不用再一步步传参去看框架源码,后续如果有时间我会给大家逐一Shiro的高级应用,大家可以关注我 其他文章: GodSchoolThread的数据结构 public class Thread implements Runnable {//每个线程中有一个变量,存储了所有本地变量对应的数据 …… /** 为什么是Map,因为同一个内线程可能存不同意义的数据,用到不同的本地变量作为key(取本地变量对象的hashcode)取引用不同的数据 */ ThreadLocal.ThreadLocalMap threadLocals = null; …… }
访问map伪代码 Thread t = Thread.currentThread(); t.threadLocals//拿到map之后,就可以用本地变量作为key取出数据
每个线程访问仅仅访问到自己线程内的ThreadLocalMap
顺序:Thread(线程)————ThreadLocalMap(线程私有)——(ThreacLocalKey(ThreadLocal变量)=>ThreacLocalValue)
ThrealLocal的应用简述:比如一次请求中,数据可以存在这个线程区域里,然后在任意地方把他拿出来(常见的用法,比如shiro利用静态方法拿到了绑定好用户信息的数据)
Ps:注意线程中存数据,是利用本地变量ThreadLocal做连接的,所以如果声明了不同的本地变量,就可以存不同类型的数据;
ThrealLocal的基本使用: /** * 声明一个本地变量,一般来讲不同的ThreadLocal对象,绑定的数据的意义不同 * 泛型是为了取出的时候不用强装 * 一般来讲是静态的,这样调用方便 */ private static final ThreadLocal<UserEntity> USER_INFO = new ThreadLocal<>(); /** * 基本API使用 */ public static void main(String[] args) { USER_INFO.set(new UserEntity(){{//线程绑定数据 setSex("M"); setUserName("GodSchool"); }}); UserEntity entity=USER_INFO.get();//取出和线程绑定的数据 System.out.println(entity);//UserEntity(userName=GodSchool, sex=M,……) USER_INFO.remove();//删除和线程绑定的数据 } Ps:线程隔离的概念:只要线程绑定了数据,无论代码运行中哪个地方拿出来的都是自己线程的数据,线程隔离
很明显就是利用静态方法拿出当前线程 public void set(T value) { Thread t = Thread.currentThread();//原来这就是线程隔离的根本原因 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value);//将当前本地变量作为key引用拿出value,一个线程可能有多个本地变量 else createMap(t, value);//如果是空在创建一个map } 然后利用当前线程拿到所有本地变量的map ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//本地变量作为key拿出来 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
显然是就是一个hashMap一样的东西
ContextProvider(用于组织数据——————将用户信息,用户的request实体,response实体用一个对象统一存起来) @Getter class ContextProvider { private HttpServletRequest request; private HttpServletResponse response; private UserEntity userEntity; ContextProvider(HttpServletRequest request, HttpServletResponse response,UserEntity userEntity) { this.request = request; this.response = response; this.userEntity=userEntity; } } LocalProvider (用于封装操作————封装了本地变量的操作方法) public class LocalProvider { /** * @原生的ThreadLocal * 泛型可以标记数据类型,取出的时候就不用强转了 */ private static final ThreadLocal<ContextProvider> USER_INFO = new ThreadLocal<>(); /** * @UserInfo相关方法 */ public static UserEntity getUserInfo() { ContextProvider contextProvider = (ContextProvider) USER_INFO.get(); if (contextProvider == null) { return null; } return contextProvider.getUserEntity(); } /** * @生命周期相关方法 */ public static void init(HttpServletRequest request, HttpServletResponse response, UserEntity userEntity) { USER_INFO.set(new ContextProvider(request, response, userEntity)); } public static void destroy() { USER_INFO.remove(); } /** * @Servlet相关方法 */ public static HttpServletRequest getRequest() { ContextProvider contextProvider = (ContextProvider) USER_INFO.get(); return contextProvider.getRequest(); } public static HttpServletResponse getResponse() { ContextProvider contextProvider = (ContextProvider) USER_INFO.get(); return contextProvider.getResponse(); } }
@Configuration public class InterceptorConfig extends WebMvcConfigurerAdapter { public void addInterceptors(InterceptorRegistry registry) { /** * @封装会话数据到ThreadLocal */ registry.addInterceptor(new HandlerInterceptor() { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token=request.getHeader("sessionId"); UserEntity userInfo=JwtUtil.parseToken(token); LocalProvider.init(request,response,userInfo); return true; } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { /** * @每次用完就清空 */ LocalProvider.destroy(); } }).addPathPatterns("/**") .excludePathPatterns("/admin/user/queryUser/**"); } }
依据我们的想法,数据已经和线程绑定存在某个区域,接下来我们就可以用静态方法取出来; public IPage<InvoiceReimbVo> queryInvoice(InvoicePo po) { UserEntity userInfo = LocalProvider.getUserInfo(); }
public static UserEntity getUserInfo() { ContextProvider contextProvider = (ContextProvider) USER_INFO.get(); if (contextProvider == null) { return null; } return contextProvider.getUserEntity(); }
【1】因此先把sessionId放到ThreadLocal中 【在登陆验证成功后执行的回调】 ShiroUtil.createTokenOnThread(user); public class ShiroUtil { …… private static final ThreadLocal<String> TOKEN = new ThreadLocal<>(); public static void createTokenOnThread(UserEntity userEntity){ TOKEN.set(JwtUtil.createJWT(userEntity)); } public static String getTokenOnThread(){ return TOKEN.get(); } }
【这是重写了sessionDao,当第一次创建session后会进入这里】 protected Serializable doCreate(Session session) { String token=ShiroUtil.getTokenOnThread(); assignSessionId(session, token); storeSession(token, session); return token; }
https://www.jianshu.com/p/3c5d7f09dfbd
https://www.jianshu.com/p/6fc3bba12f38
https://www.jianshu.com/p/98b68c97df9b
https://www.jianshu.com/p/e200e96a41a0
https://blog.csdn.net/zzg1229059735/article/details/82715741
https://blog.csdn.net/qjyong/article/details/2158097
致力于简洁的知识工程,输出高质量的知识产出,我们一起努力
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算