如何理解ThreadLocal原理?
作者:卡卷网发布时间:2025-01-10 19:31浏览数量:116次评论数量:0次
本文探讨了Session的原理及其与Cookie和Token的区别。Session通过端存储SessionID来识别用户状态,涵盖创建、存储、和销毁的完整流程。与Cookie和Token较,分析了它们在存储、安全性、生命周期和应用场景上的差异。此外,Session在高并发场景下可能面临查找效率、代码复杂性、线程安全、网络传输和性能等问题。为解决这些问题,提出使用ThreadLocal替代传Session。ThreadLocal可以减少资源开销、提升代码质量、确保线程安全、减轻传输负担,并有效应对高并发挑战。文中还介绍了ThreadLocal的原理及内存泄漏的解决方法。一、什么是Session?
Session是一种在端保存用户状态信息的机制。每个用户在与建立会话时,会为其创建一个唯一的SessionID,并将该ID存储在端的会话存储中(例如内存、数据库或文件)。客户端通过Cookie或L参数将SessionID发送到,以便可以识别用户并恢复其状态。
二、Session的工作原理
Session的工作原理通常包含以下几个步骤:创建Session:当用户第一次访问时,会为该用户创建一个Session,并生成一个唯一的标识符,称为SessionID。存储SessionID:会通过Cookie或L参数将SessionID发送给用户的浏览器。Session:浏览器会在每次请求时,将SessionID发送给,根据这个ID找到对应的Session,进而识别用户的状态。销毁Session:当用户注销、关闭浏览器或Session过期时,Session将被销毁,不再保存用户的状态信息。
三、Session的使用和常用方法
Session作用域:拥有存储数据的空间,作用范围是一次会话有效,一次会话是使用同一浏览器发送的多次请求。一旦浏览器关闭,则结束会话。可以将数据存入Session中,在一次会话的任意位置进行获取,可传递任何数据(基本数据类型、对象、、数组)。resquest.getSession():得到请求游览器(客户端)对应的session。如果没有,那么就创建应该新的session。如果有那么就返回对应的session。setAttriute(Strings,Ojecto):在session存放属性getAttriute(Strings):从session中得到s所对应的属性removeAttriute(Strings):从session中删除s对应的属性getId():得到session所对应的idinvalidate():使session立即无效setMaxInactiveInterval(inti):设置session最大的有效时间。注意,这个有效时间是两次访问所间隔的最大时间,如果超过最大的有效时间,那么这个session就失效了。
pulicclassLoginInterceptorimplementsHandlerInterceptor{
@Override
pulicooleanpreHandle(finalHttpServletRequestrequest,finalHttpServletResponseresponse,finalOjecthandler)throwsException{
HttpSessionsession=request.getSession();
Ojecttoken=session.getAttriute("token");
if(token==null){
response.sendRedirect("//toLogin");
retnfalse;
}
retntrue;
}
@Override
pulicvoidafterCompletion(finalHttpServletRequestrequest,finalHttpServletResponseresponse,finalOjecthandler,finalExceptionex)throwsException{
}
}
四、Session、Cookie和Token区别?
1.Session
<>Session>是一种在端保存用户状态信息的机制。每个用户在与建立会话时,会为其创建一个唯一的SessionID,并将该ID存储在端的会话存储中(例如内存、数据库或文件)。客户端通过Cookie或L参数将SessionID发送到,以便可以识别用户并恢复其状态。
存储位置:端。安全性:较高,因为敏感数据存储在端。生命周期:通常在用户关闭浏览器或会话超时后失效。使用场景:适用于需要在端保持用户会话状态的场景,例如购物车、用户登录状态等。
2.Cookie
<>Cookie>是一种在客户端(通常是浏览器)存储数据的小文件。通过响应头Set-Cookie将Cookie发送到客户端,客户端会在后续请求中自动包含这些Cookie。Cookie可以用于存储用户的会话信息、偏好设置等。
存储位置:客户端(浏览器)。安全性:较低,容易被窃取和篡改。可以通过设置HttpOnly和Sece属性提高安全性。生命周期:可以设置为会话Cookie(浏览器关闭后失效)或持久Cookie(设置过期时间)。使用场景:适用于需要在客户端存储少量数据的场景,例如用户偏好设置、跟踪用户活动等。
3.Token
<>Token>是一种用于身份验证的字符串,通常由生成并发送给客户端。Token常用于无状态的身份验证机制,如JSONWeToken(JWT)。客户端在每次请求时将Token发送到,通过验证Token来识别用户身份。
存储位置:客户端(可以存储在Cookie、LocalStorage或SessionStorage中)。安全性:较高,Token通常包含签名和加密信息,可以防止篡改。JWT中的签名可以验证Token的完整性和实性。生命周期:可以设置过期时间,通常需要定期刷新Token(如使用RefreshToken)。使用场景:适用于分布式和微服务架构中无状态的身份验证,特别是需要跨域的场景。总结Session:端存储用户会话状态,通过SessionID识别用户。适用于需要在端保持用户状态的场景。Cookie:客户端存储少量数据,可以用于会话和用户偏好设置。安全性较低,需要注意保护敏感信息。Token:客户端存储的身份验证字符串,常用于无状态的身份验证机制。适用于分布式和需要跨域的场景。
五、为什么不能依赖Session存储用户信息?
在项目设计和开发中,Session是用于存储用户会话信息的重要机制。用户在登录后,通常会将用户信息存储在Session中,以便在后续的请求中可以访问到这些信息。如果想在其它地方获取session中的用户信息,我们需要先获取HttpServletRequest,再通过request.getSession得到HttpSession。例如下代码:
pulicstaticUsergetSessionUser(HttpServletRequestrequest)
{
if(request.getSession().getAttriute("sessionuser")!=null)
{
retn(User)request.getSession().getAttriute("sessionuser");
}
retnnull;
}
<>然而,直接通过Session来访问用户信息在高并发和大规模中会带来一些问题和挑战。>
1.Session查找的开销
Session通常存储在的内存中,尽管它提供了快速访问机制,但每次从Session中查找用户信息也会产生开销。
内存存储和查找:每次调用request.getSession().getAttriute("sessionuser"),实际上是向的内存或持久化存储查询信息,尤其在多个请求并发时,频繁访问Session会对性能产生影响。Session存储机制:对于大规模分布式,Session有可能存储在分布式缓存中(如Redis、Memcached等)。这就引入了网络请求的延迟,进一步加剧了性能问题。
2.代码的可读性和性
直接在代码中获取Session中的信息,通常会使代码显得简单,但实际上这种方式的可性和可读性差。
重复代码:如你提到的代码示例,每次都需要调用request.getSession()和request.getSession().getAttriute(),这种方式会导致代码重复、冗长,且当项目中有多个地方需要获取Session时,可能会导致困难。封装性差:直接作Session使得代码不具备良好的封装性。每个地方都直接依赖于HttpServletRequest和Session,这违背了代码解耦的原则。
3.线程安全问题
Session在多线程环境下可能会导致线程安全问题。
并发访问:在高并发的环境中,多个请求可能同时访问或修改Session中的数据。如果Session对象不是线程安全的,就需要额外的同步机制来避免数据冲突或竞争。同步开销:为了确保线程安全,可能需要在代码中加入同步锁等机制,这不仅增加了开发复杂性,也可能带来额外的性能开销。使用同步机制可能导致线程阻塞,影响性能。
4.不必要的网络传输
在分布式中,Session通常需要在不同的之间同步,尤其是在负载均衡的场景下,用户的请求可能被分配到不同的。
Session同步问题:如果使用的是基于内存的Session存储,当一个请求的Session数据不在当前处理请求的上时,可能需要访问的Session。这种跨的通信会引入网络延迟,增加网络传输的开销。负载均衡影响:多个共享Session时,需要实现Session复制或共享机制(如stickysession、Redis共享Session等)。这些机制通常会增加额外的网络传输成本。
5.面对高并发场景
在高并发的场景下,Session的频繁访问和更新可能会导致性能瓶颈,特别是在分布式环境下。
Session频繁读写:在高并发的环境下,多个请求可能会频繁地访问Session。这不仅会增加的内存负担,还可能导致网络传输延迟,进而影响整体性能。Session集中存储压力:如果所有用户信息都存储在一个集中的Session存储中,随着用户量的增加,这种集中存储可能会成为瓶颈,特别是在高负载时,访问Session会变得缓慢,影响响应时间。
六、使用ThreadLocal替代Session存储用户信息
1、减少session查找的开销
session一般都是存储在端的,如果每次都要从session中查找用户信息,那都要去的内存或存储去查找对应的session对象,这肯定会带来一定的性能开销。那你用TheadLocal就直接把对象放在当前线程里面,想用直接在当前线程找,效率肯定就会高很多。
2、提高代码的可读性和性
直接在session里面拿用户信息,听着很直接很简单,实则需要调用多个层级才能找到,代码特别复杂且冗余,直接用TheadLocal拿用户信息肯定就更方便且直观。
3、线程安全
ThreadLocal为每个线程提供了一个的变量副本,这意味着在多线程环境下,每个线程都可以安全地访问自己的用户信息,而不会与线程发生冲突。如果直接作Session,需要额外的同步机制来保证线程安全。
4、减少不必要的网络传输
当你的用户量特别多,你不先拿到TheadLocal里面,那session里面的用户信息会越来越多,Session可能需要在多个之间同步,这会增加网络传输的开销。
5、面对高并发场景
在高并发的应用场景下,频繁地访问Session可能会导致性能瓶颈。而ThreadLocal由于其线程局部性,可以提供更好的性能表现。
七、ThreadLocal原理
ThreadLocal的实现依赖于每个线程内部的一个ThreadLocalMap对象。每个线程都有自己的ThreadLocalMap,而ThreadLocalMap中存储了所有ThreadLocal变量及其对应的值。
主要组成部分
ThreadLocal类:提供了set()、get()、remove()等方法,用于作线程局部变量。ThreadLocalMap类:是ThreadLocal的内部静态类,用于存储ThreadLocal变量及其值。Thread类:每个线程内部都有一个ThreadLocalMap实例。
工作机制
创建ThreadLocal变量:当创建一个ThreadLocal变量时,实际上并没有分配存储空间。获取值(get()方法):当调用get()方法时,当前线程会通过自己的ThreadLocalMap获取ThreadLocal变量的值。如果不存在,则调用initialValue()方法获取初始值。设置值(set()方法):当调用set()方法时,当前线程会通过自己的ThreadLocalMap设置ThreadLocal变量的值。删除值(remove()方法):当调用remove()方法时,当前线程会通过自己的ThreadLocalMap删除ThreadLocal变量的值。
使用ThreadLocal的方法
一般使用ThreadLocal的方法,就是建立一个ThreadLocal的工具类,在存储和使用用户信息时,能方便地调用。
/**
*@Author:
*@CreateTime:2025-01-07
*@Description:登录上下文对象
*@Version:1.0
*/
pulicclassThreadLocal{
privatestaticfinalInheritaleThreadLocal<Map<String,Oject>>THREAD_LOCAL
=newInheritaleThreadLocal<>();
pulicstaticvoidset(Stringkey,Ojectval){
Map<String,Oject>map=getThreadLocalMap();
map.put(key,val);
}
pulicstaticOjectget(Stringkey){
Map<String,Oject>threadLocalMap=getThreadLocalMap();
retnthreadLocalMap.get(key);
}
pulicstaticStringgetLoginId(){
retn(String)getThreadLocalMap().get("loginId");
}
pulicstaticvoidremove(){
THREAD_LOCAL.remove();
}
pulicstaticMap<String,Oject>getThreadLocalMap(){
Map<String,Oject>map=THREAD_LOCAL.get();
if(Ojects.isNull(map)){
map=newConcrentHashMap<>();
THREAD_LOCAL.set(map);
}
retnmap;
}
}
pulicclassLoginInterceptorimplementsHandlerInterceptor{
@Override
pulicooleanpreHandle(finalHttpServletRequestrequest,finalHttpServletResponseresponse,finalOjecthandler)throwsException{
StringloginId=request.getHeader("loginId");
if(loginId!=null&&loginId!=""){
ThreadLocal.set("loginId",loginId);
}
retntrue;
}
@Override
pulicvoidafterCompletion(finalHttpServletRequestrequest,finalHttpServletResponseresponse,finalOjecthandler,finalExceptionex)throwsException{
ThreadLocal.remove();
}
}
你 发表评论:
欢迎