`
vanjayzhou
  • 浏览: 1557 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

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的原理

每个线程都包含一个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.pdf

    ### 正确理解ThreadLocal:深入解析其工作原理与应用场景 #### 一、ThreadLocal的基本概念 `ThreadLocal`是Java平台提供的一种线程局部变量的解决方案,它为每一个使用该变量的线程都提供了独立的变量副本,使得每...

    使用ThreadLocal管理“session”数据

    5. **源码解析** 要深入理解ThreadLocal的工作原理,需要查看其源码。ThreadLocal内部使用了一个ThreadLocalMap,它是一个基于ThreadLocal实例作为键,值为用户存储对象的弱引用表。每个线程都有一个这样的...

    threadLocal

    - `HTTP中Get与Post的区别.doc`: 深入解析HTTP协议中的GET和POST方法的差异,可能涵盖了请求方式、数据传输、URL长度限制、缓存策略等方面。 - `用HttpClient来模拟浏览器GET_POST.doc`: 展示了如何利用HttpClient库...

    18 线程作用域内共享变量—深入解析ThreadLocal.pdf

    **ThreadLocal源代码解析** `ThreadLocal`的`get`和`set`方法实际上依赖于`Thread`类中的一个`ThreadLocalMap`。当我们调用`set`方法时,`ThreadLocal`会将值存储在当前线程的`ThreadLocalMap`中,键为`ThreadLocal`...

    threadlocal源码.jpg

    threadlocal源码解析

    Java源码解析ThreadLocal及使用场景

    Java源码解析ThreadLocal及使用场景 ThreadLocal是Java中一个非常重要的类,它在多线程环境下经常使用,用于提供线程本地变量。这些变量使每个线程都有自己的一份拷贝,使得多个线程可以独立地使用变量,不会彼此...

    ThreadLocal源码解析

    ThreadLocal类的作用:为每个线程创建独立的副本,从而保证了线程安全。 ThreadLocal使用代码示例: public class MyThreadLocalTest { private ThreadLocal threadLocal=new ThreadLocal(){ @Override protected...

    Android 详解ThreadLocal及InheritableThreadLocal

    本文将深入解析这两个概念以及它们在Android环境下的工作原理。 **ThreadLocal** 是一个线程局部变量,它为每个线程提供独立的副本。这意味着每个线程都拥有ThreadLocal变量的私有实例,它们之间互不干扰。在...

    2、导致JVM内存泄露的ThreadLocal详解

    #### 三、ThreadLocal实现解析 `ThreadLocal`类内部维护了一个`ThreadLocalMap`结构,该结构存储了线程与线程局部变量之间的映射关系。每当一个新的线程创建并首次访问某个`ThreadLocal`实例时,都会在该线程的`...

    ThreadLocal详解

    #### 重要概念解析:ThreadLocal在Java多线程中的角色 ThreadLocal是Java语言中处理多线程编程中线程安全问题的一种有效策略,它首次出现在JDK1.2版本中。与传统的使用`synchronized`关键字或`Lock`接口来实现线程...

    JAVA并发-自问自答学ThreadLocal.pdf

    4. **示例代码解析**: - 示例代码展示了如何使用ThreadLocal来创建线程唯一的标识符。每个线程首次调用`UniqueThreadIdGenerator.getCurrentThreadId()`时,会分配一个唯一的ID,并将其保存在ThreadLocal中。之后...

    InheritableThreadLocal & ThreadLocal

    《InheritableThreadLocal & ThreadLocal 深度解析》 在Java编程中,线程局部变量(ThreadLocal)和可继承线程局部变量(InheritableThreadLocal)是两种非常重要的工具,它们允许我们在多线程环境中创建独立于线程...

    8个案例详解教会你ThreadLocal.docx

    8. **面试题解析** - 在面试中,可能会遇到关于 `ThreadLocal` 生命周期管理、内存泄漏、与 `synchronized` 的比较以及在实际应用中的场景分析等问题。 通过以上介绍,我们可以了解到 `ThreadLocal` 在处理多线程...

    java ThreadLocal使用案例详解

    スレッド不安全的解析 在我们的示例代码中,我们可以看到一个典型的线程不安全的场景。在DateUtil类中,我们使用了synchronized关键字来同步parseymdhms()方法,以避免线程安全问题。然而,这种解决方案是非常不...

    Android 中ThreadLocal的深入理解

    下面我们将详细解析这两个方法的实现过程。 当我们在代码中调用`ThreadLocal.set(T value)`时,实际上发生了以下步骤: 1. 首先,获取当前运行的线程对象,即`Thread.currentThread()`。 2. 接着,调用`values()`...

    ThreadPoolExecutor源码解析.pdf

    《ThreadPoolExecutor源码解析》 ThreadPoolExecutor是Java并发编程中重要的组件,它是ExecutorService接口的实现,用于管理和调度线程的执行。理解其源码有助于我们更好地控制并发环境下的任务执行,提高系统的...

    Java ThreadLocal 线程安全问题解决方案

    例如,`SimpleDateFormat` 是非线程安全的,因为它内部使用了一个`Calendar`对象,多个线程同时使用一个`SimpleDateFormat`实例进行解析或格式化日期时,可能会出现线程A操作未完成而线程B已经开始的问题,导致数据...

    springboot基础框架,含请求参数自定义解析,动态数据源配置等功能

    // 获取当前业务需要的数据源key,例如从ThreadLocal中获取 } } ``` 最后,在`@EnableTransactionManagement`注解的配置类中,指定我们的动态数据源: ```java @Configuration @EnableTransactionManagement ...

    苍穹外卖-共享当前登录用户ID.docx

    ### 苍穹外卖系统中的ThreadLocal应用:共享当前登录用户ID #### 一、背景介绍 在现代Web应用开发中,特别是在微服务架构下,如何有效地管理用户会话信息成为了一个重要的问题。对于苍穹外卖这类应用而言,保证每...

Global site tag (gtag.js) - Google Analytics