一、 什么是ThreadLocal?
顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本。从线程的角度看,就好像每一个线程都完全拥有该变量。
它主要由四个方法组成initialValue(),get(),set(T),remove(),其中initialValue()方法是一个protected的方法,只有在重写ThreadLocal的时候有用。
void set(T t):为调用该方法的线程存入一个本线程变量。
T get(): 返回本线程存入ThreadLocal中的值,没有返回空。
void remove(): 移除本线程存入ThreadLocal中的值。
T initialValue():用于在为null时,生成一个初始值,ThreadLocal直接返回一个null值。
二、 ThreadLocal的原理
每个线程都包含一个ThreadLocal.ThreadLocalMap变量threadLocals(延迟创建的),这个映射(Map)目的就是为每个线程存储关联的不同ThreadLocal的变量和实际值。
源码如下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
三、 ThreadLocal的对象释放问题
3.1 在我们使用ThreadLocal过程中,线程结束后,它的”线程局部变量”是如何回收的呢?
首先,保存”线程局部变量”的map并非是ThreadLocal的成员变量, 而是java.lang.Thread的成员变量。也就是说,线程结束的时候,该map的资源也同时被回收。
3.2 如果线程在线程池中,一直存在,而threadLocal在多个地方被循环放入,会不会造成threadLocal对象无法回收?
如下所示:
public class TestMain { public static void main(String[] args) { while (true) { for (int j = 0; j < 10; j++) { new ThreadLocalDomail(new byte[1024*1024]).getAndPrint(); } } } } class ThreadLocalDomail{ private ThreadLocal<byte[]> threadLocal=new ThreadLocal< byte[]>(); public ThreadLocalDomail(byte[] b){ threadLocal.set(b); } public byte[] getAndPrint(){ byte[] b=threadLocal.get(); System.out.println(b.length); return b; } }
因为ThreadLocalMap的Entry是(weakReference)弱引用,在外部不再引用threadLocal对象时,线程map中 threadLocal对应的key及其value均会被释放,不会造成内存溢出。以上TestMain代码中的new ThreadLocalDomail在每次循环后即被丢弃,可被垃圾回收器回收,代码可持续运行,不会内存溢出。
四、 ThreadLocal的应用
在比较熟悉的两个框架中,Struts2和Hibernate均有采用ThreadLocal变量,而且对整个框架来说是非常核心的一部分。
Struts2和Struts1的一个重要升级就是对request,response两个对象的解耦,Struts2的Action方法中不再需要传递 request,response参数。但是Struts2不通过方法直接传入request,response对象,那么这两个值是如何传递的呢?
Struts2采用的正是ThreadLocal变量。在每次接收到请求时,Struts2在调用拦截器和action前,通过将 request,response对象放入ActionContext实例中,而ActionContext实例是作为”线程局部变量”存入 ThreadLocal actionContext中。
public class ActionContext implements Serializable { static ThreadLocal actionContext = new ThreadLocal(); . . .
由于actionContext是”线程局部变量”,这样我们通过ServletActionContext.getRequest()即可获得本线程的 request对象,而且在本地线程的任意类中,均可通过该方法获取”线程局部变量”,而无需值传递,这样Action类既可以成为一个simple类,无需继承struts2的任意父类。
在利用Hibernate开发DAO模块时,我们和Session打的交道最多,所以如何合理的管理Session,避免Session的频繁创建和销毁,对于提高系统的性能来说是非常重要的。一般常用的Hibernate工厂类,都会通过ThreadLocal来保存线程的session,这样我们在同一个线程中的处理,工厂类的getSession()方法,即可以多次获取同一个Session进行操作,closeSession方法可在不传入参数的情况下,正确关闭session。
五、 使用ThreadLocal发生的问题
在WEB服务器环境下,由于Tomcat,weblogic等服务器有一个线程池的概念,即接收到一个请求后,直接从线程池中取得线程处理请求;请求响应完成后,这个线程本身是不会结束,而是进入线程池,这样可以减少创建线程、启动线程的系统开销。
由于Tomcat线程池的原因,我最初使用的”线程局部变量”保存的值,在下一次请求依然存在(同一个线程处理),这样每次请求都是在本线程中取值而不是去memCache中取值,如果memCache中的数据发生变化,也无法及时更新。
解决方案: 处理完成后主动调用该业务treadLocal的remove()方法,将”线程局部变量”清空,避免本线程下次处理的时候依然存在旧数据。由于主动清理需要使用struts2拦截器,为了简单的解决问题,最后通过 ServletActionContext.getRequest()获取request后,将数据setAttribute进request对象中,美中不足的是和request对象有一定的耦合。
Sturts2是如何解决线程池的问题呢?
由于web服务器的线程是多次使用的,很显然Struts2在响应完成后,会主动的清除“线程局部变量”中的ActionContext值,在 struts2的 org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter类中,有这样的代码片段:
finally {
prepare.cleanupRequest(request);
}
而cleanupRequest方法中有如下代码
public void cleanupRequest(HttpServletRequest request) { ……//省略部分代码 ActionContext.setContext(null); Dispatcher.setInstance(null); }
由此可见,Sturts2在处理完成后,会主动清空”线程局部变量”ActionContext,来达到释放系统资源的目的。
六、 总结
使用ThreadLocal的几点建议:
1. ThreadLocal应定义为静态成员变量,因为静态成员变量隶属于类(全局变量),不会因为生产多个类对象而产生多分拷贝,一个静态成员变量对应一个类,而jvm中一般只加载一个相同的类,故静态成员变量在jvm中只保存了一份。各个线程共享这一份变量
2. 能通过传值传递的参数,不要通过ThreadLocal存储,以免造成ThreadLocal的滥用。
3. 在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显式的调用remove()方法,清空”线程局部变量”中的值。
4. 正常情况下使用ThreadLocal不会造成内存溢出,但如3.2中所述,弱引用的只是threadLocal,保存的值依然是强引用的,如果threadLocal依然被其他对象强引用,”线程局部变量”是无法回收的。
相关推荐
threadlocal源码解析
ThreadLocal类的作用:为每个线程创建独立的副本,从而保证了线程安全。 ThreadLocal使用代码示例: public class MyThreadLocalTest { private ThreadLocal threadLocal=new ThreadLocal(){ @Override protected...
解析源码内容,包括threadlocalmap中怎么解决hash冲突,以及set时得各种场景,还有set时什么条件会触发扩容机制,并且说明在过期桶中为什么要清除这些数据信息。
今天小编就为大家分享一篇关于Java源码解析ThreadLocal及使用场景,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
导致JVM内存泄露的ThreadLocal详解 为什么要有ThreadLocal ThreadLocal的使用 实现解析 引发的内存泄漏分析 错误使用ThreadLocal导致 线程不安全分析
Java并发编程学习宝典(漫画版),Java并发编程学习宝典(漫画版)Java并发编程学习宝典(漫画版)Java并发编程学习宝典(漫画版)Java并发编程学习宝典(漫画版)Java并发编程学习宝典(漫画版)Java并发编程学习...
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal并不是一个Thread,而是Thread的局部变量,本文就来深入解析Java中ThreadLocal线程类的作用和用法.
ThreadPoolExecutor源码解析.pdf
ThreadLocal 使用及实现原理.mp4 并发工具类CountDownLatch详解.mp4 并发工具类CyclicBarrier 详解.mp4 并发工具类Semaphore详解.mp4 并发工具类Exchanger详解.mp4 CountDownLatch,CyclicBarrier,Semaphore源码解析....
基于SpringBoot的轻量级redis事务回滚机制,使用栈和ThreadLocal记录业务链的redis操作,发生异常进行回滚,参考了阿里巴巴Seata AT模式的db回滚策略:补偿回滚,记录前镜像与当前操作语句,反向解析生成补偿动作。...
TransactionEventHandler 检查布尔 ThreadLocal 变量。 根据这一点,检查 TransactionData 是否有任何写入。 如果找到,则抛出异常以否决提交此事务。 当 Neo4j 作为服务器运行时,缺少另一部分。 我们不知何故...
no7_threadlocal ThreadLocal实例及源码解析 no8_volatile volatile实例 no9_daemon_thread 守护线程实例 no10_threadgroup 线程组 no11_thread_catch_exception 指定异常捕获类捕获线程中的异常 no12_...
InheritableThreadLocal跟ThreadLocal差不多,关键点在Thread#init方法中会对Thread中的实例变量inheritableThreadLocals进行赋值操作,将父线程的本地变量复制到子线程中 首先通过ServiceLoader.load方法生成一个...
1.什么是多线程 2.Thread类解析 3.使用多线程需要注意的问题 4.synchronized锁和lock锁 5.AQS 6.ReentrantLock和ReentrantReadWriteLock 7.线程池 8.死锁 9.线程常用的工具栏 10.Atomic 11.ThreadLocal
由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。 在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神...
主要介绍了Java多线程编程中需要注意的一些关键点,包括ThreadLocal变量与原子更新等一些深层次的内容,需要的朋友可以参考下
使用ThreadLocal自定义查询缓存 Instrument 基于Java Instrument的Agent实现 Annotation 关于Java注解的使用Annotation Spring 关于Spring底层理解 HappyParse 各种Java解析小工具 PlayCenter 业务算法小工具(抢红包...
第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...
第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节...