ThrealLocal概念理解
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值,也就是是说可以让线程与保存值的关联起来,让每个线程都有一个自己的变量,实现线程安全。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
在代码清单4-15所示的例子中,构建了一个常用的Profiler类,它具有begin()和end()两个 方法,而end()方法返回从begin()方法调用开始到end())方法被调用时的时间差,单位是毫秒。
代码清单4-15 Profiler.java
public class Profiler { // 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
private static final ThreadLocal < Long > TIME_THREADLOCAL = new ThreadLocal < Long > () {
protected Long initialValue() {
return System.currentTimeMillis();
}
};
public static final void begin() {
TIME_THREADLOCAL.set(System
.currentTimeMillis()
);
}
public static final long end() {
return System.currentTimeMillis() -
TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws Exception {
Profiler.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + Profiler.end() + " mills");
}
}
Profiler可以被复用在方法调用耗时统计的功能上,在方法的入口前执行begin()方法,在
方法调用后执行end()方法,好处是两个方法的调用不用在一个方法或者类中,比如在AOP(面 向方面编程)中,可以在方法调用前的切入点执行begin()方法,而在方法调用后的切入点执行 end()方法,这样依旧可以获得方法的执行耗时
多线程初次访问某个ThreadLocal 的get方法时候会调用 initalValue 方法进行变量的初始化,如果没有set值的时候会get到null 值,因此可以通过 initalValue 方法进行过初始化,避免null值,有两种方法。
方法一继承ThrealLocal类,重写 initalValue 方法
方法一
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return "我是默认值 第一次get不再为null";
}
}
public class Run {
public static ThreadLocalExt tl = new ThreadLocalExt();
public static void main(String[] args) {
if (tl.get() == null) {
System.out.println("从未放过值");
tl.set("我的值");
}
System.out.println(tl.get());
System.out.println(tl.get());
}
}
方法二 在new ThrealLocal对象的时候从写 initalValue 方法 赋初值,
private static final ThreadLocal < Long > TIME_THREADLOCAL = new ThreadLocal < Long > () {
protected Long initialValue() {
return System.currentTimeMillis();
}
};
ThreadLocal 常用方法
void set(T t):为调用该方法的线程存入一个本线程变量。
T get(): 返回本线程存入ThreadLocal中的值,没有返回空。
void remove(): 移除本线程存入ThreadLocal中的值。
T initialValue():用于在为null时,生成一个初始值,ThreadLocal直接返回一个null值。
ThreadlLocal原理和引用问题
转载见
https://blog.csdn.net/weixin_39966941/article/details/111360434
ThreadLocalMap实例是作为java.lang.Thread的成员变量存储的,每个线程有唯一的一个threadLocalMap。这个map以ThreadLocal对象为key,”线程局部变量”为值,所以一个线程下可以保存多个”线程局部变量”。对ThreadLocal的操作,实际委托给当前Thread,每个Thread都会有自己独立的ThreadLocalMap实例,存储的仓库是Entry[] table;Entry的key为ThreadLocal,value为存储内容;因此在并发环境下,对ThreadLocal的set或get,不会有任何问题
将ThreadLocalMap作为Thread类的成员变量的好处是:
a. 当线程死亡时,threadLocalMap被回收的同时,保存的”线程局部变量”如果不存在其它引用也可以同时被回收。
b. 同一个线程下,可以有多个treadLocal实例,保存多个”线程局部变量”。
因为ThreadLocalMap的Entry是(weakReference)弱引用,在外部不再引用threadLocal对象时,线程map中threadLocal对应的key及其value均会被释放,不会造成内存溢出。以上TestMain代码中的new ThreadLocalDomail在每次循环后即被丢弃,可被垃圾回收器回收,代码可持续运行,不会内存溢出。
ThreadLocal的应用
四、 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();
. . .
代码片段4
由于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);
}
代码片段6
由此可见,Sturts2在处理完成后,会主动清空”线程局部变量”ActionContext,来达到释放系统资源的目的。
六、 总结
使用ThreadLocal的几点建议:
-
ThreadLocal应定义为静态成员变量,代码片段3中的定义方式是不提倡的。
-
能通过传值传递的参数,不要通过ThreadLocal存储,以免造成ThreadLocal的滥用。
-
在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显式的调用remove()方法,清空”线程局部变量”中的值。
-
正常情况下使用ThreadLocal不会造成内存溢出,弱引用的只是threadLocal,保存的值依然是强引用的,如果threadLocal依然被其他对象强引用,”线程局部变量”是无法回收的。
以上是本人对ThreadLocal对象的一些了解,如有不足,还请指正。