当前位置:首页 > 每日看点 > 正文内容

如何理解ThreadLocal原理?

卡卷网7个月前 (01-10)每日看点135

本文探讨了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; } }

八、ThreadLocal的内存泄漏问题

在使用ThreadLocal时,要注意ThreadLocal地内存泄漏问题。

ThreadLocal的内存泄漏的原因

ThreadLocal的内存泄漏问题主要源于ThreadLocalMap中使用的弱引用(WeakReference)机制和线程生命周期不当这两个原因。

    弱引用机制:ThreadLocalMap使用Entry类来存储键值对,其中键是ThreadLocal对象的弱引用(WeakReference<ThreadLocal<?>>),值是实际存储的数据。当ThreadLocal对象被回收时,弱引用会被清除,但Entry中的值对象仍然存在,导致内存无法及时释放。线程生命周期:在一些长生命周期的线程(如线程池中的线程)中,如果不显式地清除ThreadLocal变量,ThreadLocalMap中的值对象会一直存在,导致内存泄漏。线程池中的线程不会在任务完成后立即销毁,而是会被复用。如果ThreadLocal变量没有被显式清除,下一个使用该线程的任务可能会意外地访问到上一个任务遗留的数据。

如何避免ThreadLocal的内存泄漏?

最直接和有效的方法是在使用完ThreadLocal变量后,显式调用remove()方法清除变量。

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(); } }

欢迎关注❤

我们搞了一个免费的面试题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:知乎面试群。

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/01/8617.html

分享给朋友:

相关文章

WordPress建设的网站为什么不推荐国内机房?

WordPress建设的网站为什么不推荐国内机房?

我劝大家不要用WordPress做国内网站,不要用国内机房的主机。原因如下:1 WordPress 很多主题和插件都是国外开发的,特别是付费版本,需要联网验证,或者远程写入。国内机房的虚拟主机和服务器大概率会屏蔽或者阻断这些连接,无法完成任...

电脑c盘哪些文件可以删除?

电脑c盘哪些文件可以删除?

电脑上的文件夹都是英文,很多朋友都不敢乱删,下面这几个文件夹里的文件,你可以放心删除。一、可删除的文件1、Backup这是一个备份文件夹,很多装机软件经常会把需要备份的东西,放在这个文件夹中。而当我们需要的软件正常保存之后,这些东西也就没有...

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

感觉手机配置都差不多,为什么有的手机能卖2k-3k,而有的手机却能卖到6k-8k?

与所有的商品一样,手机的价格,也是由它的成本所决定的。虽然看起来3000元的手机和6000的手机配置差不多,甚至处理器都可能是同一个,但在很多大家容易忽略的地方,决定了两者价格的不同:例如手机的外观,塑料的机身,与素皮机身和玻璃机身就完全不...

小米15就要来了,不知道小米15值不值得买?

小米15就要来了,不知道小米15值不值得买?

俗话说得好,好饭不怕晚,看似有点姗姗来迟的小米15系列,一官宣就迅速登上热搜。在此次小米14发布会上,雷军曾说过小米14将是最后一代3999起的旗舰,那么这一次涨价了的小米15,值不值得买呢?下面为大家总结一下小米15将会有哪些升级点:软件...

用红米手机会很丢人吗?

前些日子遇到了一位快递小哥,京东的,签收小哥年纪不算大,目测二十多岁他的手机上全是一道道极严重的划痕,有点卡,他开热点了,热点名就是手机型号,红米9a,我父母的同款现在他那边操作了一会,然后又是我这边操作了一会小哥看着我手机刷刷的,颇有些好...

如何判断 Java 工程师的基础知识是否扎实?

我来给你出几道大题,能答对70%,你就算基础扎实了。第一部分 Java基础(27)1. 程序本质:代码是如何被执行的?CPU、操作系统、虚拟机各司何职?2. 基础语法:从CPU角度看变量、数组、类型、运算、跳转、函数等语法3. 引用类型:同...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。