`

关于ThreadLocal模式的体会

阅读更多

写这篇帖子的目的不是为了来剖析ThreadLocal,因为坛子里有许多高手已经深入浅出的把ThreadLocal讲解的很清楚了。

特别是lujh99正确理解ThreadLocal这篇帖子,通过JDK源代码把ThreadLocal讲得非常深入浅出,让我深受启发。我写这篇帖子的目的只是为再此作一个补充,想以另外一种通俗易懂的表达方式把自己对ThreadLocal理解写出来。

 

 

lujh99 写道
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

  

 正如lujh99 所言,ThreadLocal不是用来解决对象共享访问问题的,而是为了处理在多线程环境中,某个方法处理一个业务,需要递归依赖其他方法时,而要在这些方法中共享参数的问题。例如有方法a(),在该方法中调用了方法b(),而在b方法中又调用了方法c(),即a-->b--->c,如果a,b,c都需要使用用户对象,那么我们常用做法就是a(User user)-->b(User user)---c(User user)。但是如果使用ThreadLocal我们就可以用另外一种方式解决:

  1.   在某个接口中定义一个静态的ThreadLocal 对象,例如 public static ThreadLocal  threadLocal=new ThreadLocal ();
  2.   然后让a,b,c方法所在的类假设是类A,类B,类C都实现1中的接口
  3.   在调用a时,使用A.threadLocal.set(user) 把user对象放入ThreadLocal环境
  4.   这样我们在方法a,方法b,方法c可以在不用传参数的前提下,在方法体中使用threadLocal.get()方法就可以得到user对象。

   上面的类A,类B ,类C就可以分别对应我们做web开发时的 web层的Action--->业务逻辑层的Service-->数据访问层的DAO,当我们要在这三层中共享参数时,那么我们就可以使用ThreadLocal 了。

 

    那么user对象是如何存放到ThreadLocal 中的?

lujh99 写道
将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

   

    经过我的测试,正是如此,当我们调用A.threadLocal.set(user) 时,set()做了以下几件事:

  1. 得到当前线程Thread 对象
  2. 通过当前线程对象得到当前线程的ThreadLocalMap 对象
  3. 将ThreadLocal静态实例作为key,user对象作值,存放到ThreadLocalMap 中。

PS:因为ThreadLocal是静态的,所以每个线程中的ThreadLocalMap的key都是相同的,不同的只是存放的容器ThreadLocalMap。

 

总结:

   其实ThreadLocal 跟我们做web开发时使用的session对象的作用很类似,每当我们向服务器发送一个请求时,web服务器会为该请求创建一个线程,同时会为该请求创建一系列对象,其中包括session(当然在同一个浏览器发送的请求都获得是同一个session对象),所以当我们做web开发时,可以不用担心线程安全问题,自由的往session中存取变量,保存用户状态。同理,当我们在程序中第一次使用A.threadLocal.set(user) 存放参数时,不管在程序的哪个地方,我们都可以通过ThreadLocal  所在的接口访问静态threadLocal对象,同时来共享threadLocal存放的参数,threadLocal就相当于session的作用,来保存当前线程的状态。在我们开发实际开发中,可以任意往threadLocal中共享和存取自己需要的变量,只不过web中的session的生命周期在于客户端的浏览器,而threadLocal中存储的变量的生命周期只在于当前线程,当前结束,threadLocal中存放的参数也被销毁。

 

 

 

分享到:
评论
36 楼 C_J 2010-12-05  
我也有不明白的地方。

ThreadLocal的本意是配合Thread以一种对象拷贝的方式解决多线程的互斥问题的。各自Thread维护自己的ThreadLocalMap,并以这个ThreadLocal的hashCode为key,用户对象为value。

如果lz用一个static的ThreadLocal,以这个ThreadLocal的hashcode()为key,User为value,并只在同一个线程里发挥作用的话,跟用public static Map,以Thread.currentThread().hashCode()为key,User为value,有什么区别吗?



引用

threadlocal内部貌似是使用了Thread.currentThread().getName()来当做key,在jdk1.5中是一种效率比较低的做法,据说在6.0速度有提升,但是实际中用途也不是很广泛,只有在权限系统或者保存数据库Connection时才会用到。


  额,你听谁说的?? 
JDK1.5的代码貌似如下:

    private static synchronized int nextHashCode() {
        int h = nextHashCode;
        nextHashCode = h + HASH_INCREMENT;
        return h;
    }
35 楼 taolei0628 2010-12-05  
“而threadLocal中存储的变量的生命周期只在于当前线程,当前结束,threadLocal中存放的参数也被销毁”
这句话虽然说的没错,但千万不要利用线程销毁来清除ThreadLocal,正确的方法就像前面的doFilter例子那样,谁设置谁要负责清除。
所有服务器环境下都是用线程池缓存线程,由于线程不会(或不会立即)被销毁,ThreadLocal也不会被清除,更严重的问题是被缓存的线程的ThreadLocal不确定的会被用到其他服务请求处理中。
假如用ThreadLocal设置了user,而不及时清除,甚至会发生比较严重的安全问题。
34 楼 aaa5131421 2010-12-04  
抛出异常的爱 写道
LucasLee 写道
61234865 写道
我有一个地方不太明白,就是我们用SSH的时候,如果你用threadlocal来存变量,你第二次请求的时候怎么能保证获取到的线程就是和先前的线程一样的?如果不一样,那数据岂不是就丢失了?


这个问题的关键在于在web环境下怎么使用。
一般是使用Filter技术,在web.xml配置好,filter里的伪代码是这样:

doFilter(){
设置threadlocal变量;
filterChain.doFilter();
清除threadlocal变量;
}



这样就保证了在Action、JSP里都可以获得本线程的threadlocal变量。
注意:一次web 请求肯定是由一个线程来完成响应的。

这样的用法就更清析了...
不知道还有可能会出什么问题.....
不想用了新的用法会出什么隐问题

这样用就没问题了
doFilter(){
设置threadlocal变量;
try {
filterChain.doFilter();
}finally {
清除threadlocal变量;}
}
33 楼 dingchao.lonton 2010-12-04  
比传参作用大吧,传参只在这个线程的一个方法里面有用,但是threadlocal可以贯穿整个线程,每个方法在使用的时候都可以得到,类似于spring opensessiononview吧
32 楼 xihuyu2000 2010-09-12  
<div class="quote_title">dominic6988 写道</div>
<div class="quote_div">
<p>[b][/b]</p>
<div class="quote_title">抛出异常的爱 写道</div>
<div class="quote_div">
<div class="quote_title">61234865 写道</div>
<div class="quote_div">我有一个地方不太明白,就是我们用SSH的时候,如果你用threadlocal来存变量,你第二次请求的时候怎么能保证获取到的线程就是和先前的线程一样的?如果不一样,那数据岂不是就丢失了?</div>
<br>同次请求的不丢就很了不起了. <br>
</div>
<p> </p>
<p> 第二次请求的时候和第一次请求的时候用的是同一个servlet实例。servlet是线程安全的对同一个servlet的请求容器只会实例会一个对像。</p>
</div>
<br>

开发是分层的,不仅jsp和servlet之间传递参数,包括service层、dao层之间传递
31 楼 pouyang 2010-09-10  
niveko 写道
ThreadLocal不是用来解决对象共享访问问题的
我不太同意这个观点,现在比如有如下的代码。这个format方法有2个线程循环的访问,每次访问完可以放回线程池中,但是因为SimpleDateFormat不是线程安全的类,所以这样访问肯定会出现并发的错误!
public class Foo {
	
	static SimpleDateFormat formator = new SimpleDateFormat("yyMMddHHmmss");
	
	public static String format(Date date) {
		return formator.format(date);
	}

}


那么为了避免并发的错误,可以有如下2中方案,1加synchronized,这样每次只有一个线程访问这个方法,对性能有影响
public static synchronized String format(Date date) {
		return formator.format(date);
	}

2,每次调用这个方法就生成一个新的SimpleDateFormat对象,这样会生成大量的对象,对性能不是很好
public static String format(Date date) {
		SimpleDateFormat formator = new SimpleDateFormat("yyMMddHHmmss");
		return formator.format(date);
	}


那么如果用ThreadLocal就可以解决创建大量对象的问题和并发访问的问题,重复利用SimpleDateFormat对象
public class Foo {
	static ThreadLocal local = new ThreadLocal();
	
	public static String format(Date date) {
		SimpleDateFormat formator = (SimpleDateFormat)local.get();
		if (formator == null) {
			formator = new SimpleDateFormat("yyMMddHHmmss");
			local.set(formator);
		}
		return formator.format(date);
	}

}



这个我赞同
30 楼 dominic6988 2010-04-23  
<p>[b][/b]</p>
<div class="quote_title">抛出异常的爱 写道</div>
<div class="quote_div">
<div class="quote_title">61234865 写道</div>
<div class="quote_div">我有一个地方不太明白,就是我们用SSH的时候,如果你用threadlocal来存变量,你第二次请求的时候怎么能保证获取到的线程就是和先前的线程一样的?如果不一样,那数据岂不是就丢失了?</div>
<br>同次请求的不丢就很了不起了. <br>
</div>
<p> </p>
<p> 第二次请求的时候和第一次请求的时候用的是同一个servlet实例。servlet是线程安全的对同一个servlet的请求容器只会实例会一个对像。</p>
29 楼 niveko 2010-04-01  
ThreadLocal不是用来解决对象共享访问问题的
我不太同意这个观点,现在比如有如下的代码。这个format方法有2个线程循环的访问,每次访问完可以放回线程池中,但是因为SimpleDateFormat不是线程安全的类,所以这样访问肯定会出现并发的错误!
public class Foo {
	
	static SimpleDateFormat formator = new SimpleDateFormat("yyMMddHHmmss");
	
	public static String format(Date date) {
		return formator.format(date);
	}

}


那么为了避免并发的错误,可以有如下2中方案,1加synchronized,这样每次只有一个线程访问这个方法,对性能有影响
public static synchronized String format(Date date) {
		return formator.format(date);
	}

2,每次调用这个方法就生成一个新的SimpleDateFormat对象,这样会生成大量的对象,对性能不是很好
public static String format(Date date) {
		SimpleDateFormat formator = new SimpleDateFormat("yyMMddHHmmss");
		return formator.format(date);
	}


那么如果用ThreadLocal就可以解决创建大量对象的问题和并发访问的问题,重复利用SimpleDateFormat对象
public class Foo {
	static ThreadLocal local = new ThreadLocal();
	
	public static String format(Date date) {
		SimpleDateFormat formator = (SimpleDateFormat)local.get();
		if (formator == null) {
			formator = new SimpleDateFormat("yyMMddHHmmss");
			local.set(formator);
		}
		return formator.format(date);
	}

}


28 楼 池中物 2010-04-01  
我通常把User放到ThreadLocal中
27 楼 kwuly 2010-04-01  
threadlocal 应该只用来解决多线程问题, 如果用来解决参数传递问题, 表面上看.影响了业务分层. 实际上也给以后扩展带来隐患. 比如说service层或者数据访问层写好后因为需要,想提供给对外接口复用.问题就来了
26 楼 mercyblitz 2010-03-22  
yimlin 写道
通常用来解决框架中的上下文对象,业务级的通常还是传参解决,这样业务上更为明确。



很赞同你的观点。
25 楼 mercyblitz 2010-03-22  
抛出异常的爱 写道
不知道有哪个项目是这么用的.

可以解决很多参数问题.



举一个例子-CXF,很多很多。
24 楼 wlwolf 2010-03-19  
x_root 写道
webwork和struts2都用了这个东西。

是地,以前给项目国际化的时候用过这个。可以把rquest放在ThreadLocal里面,这样在任何方法内都可以得到当前登录用户,有时定义某个接口可能忽略了一些参数,可以这样做。。。
23 楼 x_root 2010-03-18  
webwork和struts2都用了这个东西。
22 楼 vivide 2010-03-18  
<p>我还是觉得,像这样用超出了ThreadLocal的意图。</p>
<p>如果在一个业务流程真的那么需要一个context存储上下文变量,那么我们应该找到一个map,把 “流程id映射map” 存储进去。</p>
<p>这样可以完全抛开Thread的概念,业务方法就不会把底层的东西都依赖进来。</p>
<p> </p>
<p>ThreadLocal还是应该应用在处理多线程的问题中。</p>
<p> </p>
<p> </p>
21 楼 抛出异常的爱 2010-03-18  
LucasLee 写道
61234865 写道
我有一个地方不太明白,就是我们用SSH的时候,如果你用threadlocal来存变量,你第二次请求的时候怎么能保证获取到的线程就是和先前的线程一样的?如果不一样,那数据岂不是就丢失了?


这个问题的关键在于在web环境下怎么使用。
一般是使用Filter技术,在web.xml配置好,filter里的伪代码是这样:

doFilter(){
设置threadlocal变量;
filterChain.doFilter();
清除threadlocal变量;
}



这样就保证了在Action、JSP里都可以获得本线程的threadlocal变量。
注意:一次web 请求肯定是由一个线程来完成响应的。

这样的用法就更清析了...
不知道还有可能会出什么问题.....
不想用了新的用法会出什么隐问题
20 楼 LucasLee 2010-03-18  
61234865 写道
我有一个地方不太明白,就是我们用SSH的时候,如果你用threadlocal来存变量,你第二次请求的时候怎么能保证获取到的线程就是和先前的线程一样的?如果不一样,那数据岂不是就丢失了?


这个问题的关键在于在web环境下怎么使用。
一般是使用Filter技术,在web.xml配置好,filter里的伪代码是这样:

doFilter(){
设置threadlocal变量;
filterChain.doFilter();
清除threadlocal变量;
}



这样就保证了在Action、JSP里都可以获得本线程的threadlocal变量。
注意:一次web 请求肯定是由一个线程来完成响应的。
19 楼 lkf520java 2010-03-18  
<div class="quote_title">vivide 写道</div>
<div class="quote_div">
<p>可以这么做,不过个人理解这是对ThreadLocal的误用。</p>
<p> </p>
<p>ThreadLocal充当当前线程的Context,同时也可以理解为ThreadLocal变量的作用域属于Thread。</p>
<p>业务方法的参数的生命周期可能是瞬态的,无端的放入ThreadLocal中会延长这些对象的生命周期,注意回收。</p>
<p>好的,我们会及时清理,如果真要这样做,还需要再写代码,不知有何优点。</p>
<p>同时这些业务方法没有了参数的描述会变得晦涩,还被强制的要求在同一线程的原理下工作。抽象依赖实现,不可取。</p>
<p> </p>
<p>PS:</p>
<p>“ThreadLocal不是用来解决对象共享访问问题的”------如果Java没有了多线程,大家还需要ThreadLocal么。</p>
<p>ThreadLocal 和 request的作用域完全是不同的。</p>
<p> </p>
</div>
<p> <br>其实对于ThreadLocal的这种特性,该如何使用,我觉得这个大家各有所需,各有所好,我就不在强调自己的观点。</p>
<p> </p>
<p>我也非常肯定你的观点:ThreadLocal 和request的作用域肯定是不相同的,ThreadLocal 中存放的变量的生命周期只存活于当前线程,而request的作用域在于一个请求处理结束。在tomcat的线程池环境下,基本上都是request处理完成后,线程都被回放到线程池,即request作用域结束,ThreadLocal 中ThreadLocalMap存放的变量也并没有被销毁。我说的是可以认为ThreadLocal 和 request的作用域是相同的 ,因为即使线程没被回收,我们也无非再找回之前处理过的线程,即使找回那个线程,那个线程也许被另外的request使用过,里面存放的是其他的参数,所以我在这里假设ThreadLocal 和 request的作用域是相同的是为了让大家明白,ThreadLocal 在实际使用过程中最好且也只有用于一次请求处理。</p>
<p> </p>
18 楼 lkf520java 2010-03-18  
allengao 写道
我记得过去测试过一次,在web中使用threadlocal保存变量,结果在线程消失后,变量值并没有自动清除,貌似request线程下,需要自己手动清除才可以,然后我们用threadlocal更多的情况下是为了保存dao,使其单例才可以完成方法的事务的完整性传播特性,用户对象这种个人认为没有必要threadlocal来保存。


因为一般的servlet,JSP容器,都使用的线程池技术,所以当一个请求处理完成直到向客户端发送响应结束后,调度这个请求处理的线程没有被销毁而被放回线程池,所以threadlocal保存的变量也并没有被销毁,必须我们手动清除。
17 楼 vivide 2010-03-17  
<p>可以这么做,不过个人理解这是对ThreadLocal的误用。</p>
<p> </p>
<p>ThreadLocal充当当前线程的Context,同时也可以理解为ThreadLocal变量的作用域属于Thread。</p>
<p>业务方法的参数的生命周期可能是瞬态的,无端的放入ThreadLocal中会延长这些对象的生命周期,注意回收。</p>
<p>好的,我们会及时清理,如果真要这样做,还需要再写代码,不知有何优点。</p>
<p>同时这些业务方法没有了参数的描述会变得晦涩,还被强制的要求在同一线程的原理下工作。抽象依赖实现,不可取。</p>
<p> </p>
<p>PS:</p>
<p>“ThreadLocal不是用来解决对象共享访问问题的”------如果Java没有了多线程,大家还需要ThreadLocal么。</p>
<p>ThreadLocal 和 request的作用域完全是不同的。</p>
<p> </p>

相关推荐

Global site tag (gtag.js) - Google Analytics