JAVA ThreadLocal 对象 ServletActionContext

最近在开发过程中,在做一个字典项服务的时候,最开始采用了ThreadLocal对象来缓存数据。在使用ThreadLocal过程中遇到一些问题,这里和大家分享一下。

一、 什么是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的原理

在查看了java源码后发现,ThreadLocal通过使用ThreadLocalMap(注:这里的Map非java.util.Map子类)实例来存储”线程局部变量”,当第一次设值的时候,如果map为空,则创建一个map并set入值,但是这个储值的Map并非ThreadLocal的成员变量,而是java.lang.Thread
类的成员变量。ThreadLocal的set,get方法源码如下:

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

代码片段1

三、 ThreadLocal的对象释放问题

3.1 在我们使用ThreadLocal过程中,线程结束后,它的”线程局部变量”是如何回收的呢?

首先,保存”线程局部变量”的map并非是ThreadLocal的成员变量, 而是java.lang.Thread的成员变量。也就是说,线程结束的时候,该map的资源也同时被回收。

解析:

ThreadLocal的set,get方法中均通过如下方式获取Map:

ThreadLocalMap map = getMap(t);

而getMap方法的代码如下:

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

代码片段2

可见:ThreadLocalMap实例是作为java.lang.Thread的成员变量存储的,每个线程有唯一的一个threadLocalMap。这个map以ThreadLocal对象为key,”线程局部变量”为值,所以一个线程下可以保存多个”线程局部变量”。对ThreadLocal的操作,实际委托给当前Thread,每个Thread都会有自己独立的ThreadLocalMap实例,存储的仓库是Entry[]
table;Entry的key为ThreadLocal,value为存储内容;因此在并发环境下,对ThreadLocal的set或get,不会有任何问题。以下为”线程局部变量”的存储图:

“线程局部变量”的存储图

由于treadLocalMap是java.util.Thread的成员变量,threadLocal作为threadLocalMap中的key值,在一个线程中只能保存一个”线程局部变量”。将ThreadLocalMap作为Thread类的成员变量的好处是:

a. 当线程死亡时,threadLocalMap被回收的同时,保存的”线程局部变量”如果不存在其它引用也可以同时被回收。

b. 同一个线程下,可以有多个treadLocal实例,保存多个”线程局部变量”。

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

代码片段3

因为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();
. . .

代码片段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的几点建议:

1. ThreadLocal应定义为静态成员变量,代码片段3中的定义方式是不提倡的。

2. 能通过传值传递的参数,不要通过ThreadLocal存储,以免造成ThreadLocal的滥用。

3. 在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显式的调用remove()方法,清空”线程局部变量”中的值。

4. 正常情况下使用ThreadLocal不会造成内存溢出,但如3.2中所述,弱引用的只是threadLocal,保存的值依然是强引用的,如果threadLocal依然被其他对象强引用,”线程局部变量”是无法回收的。

以上是本人对ThreadLocal对象的一些了解,如有不足,还请指正。

时间: 2024-10-13 11:25:48

JAVA ThreadLocal 对象 ServletActionContext的相关文章

14、Java并发性和多线程-Java ThreadLocal

以下内容转自http://ifeve.com/java-theadlocal/: Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域. 1.创建一个ThreadLocal对象 如下所示,创建一个ThreadLocal变量: private ThreadLocal myThreadLocal = n

Java ThreadLocal 深入剖析

最近看Android FrameWork层代码,看到了ThreadLocal这个类,有点儿陌生,就翻了各种相关博客一一拜读:自己随后又研究了一遍源码,发现自己的理解较之前阅读的博文有不同之处,所以决定自己写篇文章说说自己的理解,希望可以起到以下作用: 可以疏通研究结果,加深自己的理解 可以起到抛砖引玉的作用,帮助感兴趣的同学疏通思路 分享学习经历,和大家一起交流和学习 一. ThreadLocal 是什么 ThreadLocal 是Java类库的基础类,在包java.lang下面: 官方的解释是

java ThreadLocal(应用场景及使用方式及原理)

尽管ThreadLocal与并发问题相关,可是很多程序猿只将它作为一种用于"方便传參"的工具,胖哥觉得这或许并非ThreadLocal设计的目的,它本身是为线程安全和某些特定场景的问题而设计的. ThreadLocal是什么呢. 每一个ThreadLocal能够放一个线程级别的变量,可是它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全. 比如: public final static ThreadLocal<String> RESOURCE = n

Java ThreadLocal

背景: 最近项目中需要调用其他业务系统的服务,使用的是Java的RMI机制,但是在调用过程中中间件发生了Token校验问题.而这个问题的根源是每次用户操作,没有去set Token导致的.这个Token是存储在ThreadLocal变量中的,根据servlet的单例多线程原理,使用一个拦截器每次向Thread中写入这个token完美的解决了这个问题. ThreadLocal ThreadLocal是Java lang包里面的一个类,这个类用来提供线程级变量的实现.想要明白ThreadLocal首

深入研究JAVA ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量 (ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程

java ThreadLocal解读

Thread.java源码中: ThreadLocal.ThreadLocalMap threadLocals = null; 即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用. static class ThreadLocalMap { 所以每个线程都会有一个ThreadLocal.ThreadLocalMap对

多线程学习(三)Java ThreadLocal

Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量.因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量. 如何创建ThreadLocal变量 以下代码展示了如何创建一个ThreadLocal变量: 1 private ThreadLocal myThreadLocal = new ThreadLocal(); 我们可以看到,通过这段代码实例化了一个ThreadLocal对象.我们只需要

【Java ThreadLocal的使用】

原文链接  作者: Jakob Jenkov 译者: 林威建[[email protected]] Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量.因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量. 如何创建ThreadLocal变量 以下代码展示了如何创建一个ThreadLocal变量: [size=1em][size=1em] 1 private ThreadLocal my

Java ThreadLocal的使用

1.ThreadLocal用来解决多线程程序的并发问题2.ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本.3.从线程的角度看,目标变量就象是线程的本地变量,这也是类名中"Local"所要表达的意思.4.线程局部变量并不是Java的新发明,Java没有提供在语言级支持(语法上),而是变相地