java中threadlocal_Thread Local Storage模式和Java中的ThreadLocal

线程同步是进行多线程编程时所必须考虑的一个问题。之所以要进行同步,是因为多个线程需要访问共享资源,典型的是共享内存数据。如果能为每个线程提供一份需要共享的数据的copy,那么对该数据的访问也就没有必要进行同步了。Thread Local Storage就是能够达到这个目的的一个多线程设计模式。Thread Local Storage,顾名思义,就是“线程本地数据”,指每个线程拥有各自独立的数据拷贝。Thread Local Storage还有另外一些称呼:Thread Specific Storage,Thread Specific Data等。

Java类库中的ThreadLocal类就是该模式的一个实现。在该类的api文档中有如此说明:ThreadLocal类型的变量不同于普通变量,每个访问它的线程都有一份各自独立初始化的copy,对它的访问是通过get/set方法实现的。ThreadLocal实例典型情况下是类的private static字段。

下面一段程序使用了ThreadLocal类。这个程序假定用于对网站的访问日志进行某种处理,如果有N个日志文件需要处理,就启动N个线程,需要记录处理每个日志文件所花费的时间。

01public class LogStats {

02private static final ThreadLocal logFile =newThreadLocal();

03private static final ThreadLocal startTime =newThreadLocal();

04

05public static void init(String logFileName) {

06logFile.set(logFileName);

07startTime.set(System.currentTimeMillis());

08}

09

10public static void process() {

11open(logFile.get());

12readAndProcessEachRecord();

13long time = System.currentTimeMillis() - startTime .get();

14System.out.println(logFile.get() +" processed: " + time +"ms.");

15}

16

17public static void main(String[] args) {

18for (final String logFileName : args) {

19new Thread() {

20public void run() {

21LogStats.init(logFileName);

22LogStats.process();

23}

24}.start();

25}

26}

27}

为简明起见,省略了部分代码。执行这个程序,可能会产生如下输出结果:

1java LogStats access.log.1 access.log.2 access.log.3

2access.log.1 processed: 1294538ms

3access.log.2 processed: 1265327ms

4access.log.3 processed: 1189563ms

在get/set方法被调用时,ThreadLocal能根据执行的上下文(即Thread.currentThread())确定变量的值。

从外观上看,ThreadLocal类似一个HashMap,其键相当于Thread.currentThread(),值为线程的变量值。据此实现一个自定义版本的ThreadLocal类型是比较直接的:

01public class CustomizedThreadLocal {

02private Map map = Collections.synchronizedMap(newHashMap());

03protected T initialValue () {

04return null;

05}

06

07public T get() {

08if (!map.containsKey(Thread.currentThread())) {

09map.put(Thread.currentThread(), initialValue());

10}

11return map.get(Thread.currentThread());

12}

13

14public void set(T value) {

15map.put(Thread.currentThread(), value);

16}

17

18public void remove() {

19map.remove(Thread.currentThread());

20}

21}

可以使用CustomizedThreadLocal来代替LogStats中的ThreadLocal的功能,当然效率上可能会有所差别。

在LogStats代码中,对logFile和startTime的访问没有进行显式的同步。但这并不意味着使用Thread Local Storage模式时,可以完全消除同步,只不过是在应用层代码中可以不进行同步。在ThreadLocal的内部,线程同步可能发生在对各线程变量值的访问时,就像在CustomizedThreadLocal中所做的一样(通过sychronizedMap达到同步)。查看Java类库的ThreadLocal的源码发现,对ThreadLocal的访问是这样的:JVM为每个Thread维护一个该线程独有变量的Map,称为ThreadLocalMap,这是通过本地代码(native方法)来实现的,当访问ThreadLocal时,根据ThreadLocal实例的Hash值在当前线程的ThreadLocalMap中查找对应的变量值,而一个线程对其自身的ThreadLocalMap的访问必然是单线程的而无须同步。不过在本地代码中,JVM底层可能会施加某些同步措施。

LogStats的代码只是为了演示ThreadLocal的使用,并不是良好的OO设计风格。这里使用ThreadLocal仅只是记录执行时间,没有什么实际意义,似乎完全没有必要。但是考虑在一个更大的应用程序范围内,线程的执行可能会跨越很深的代码层次结构,比如在一个基于servlet的web应用中处理用户请求的线程,要使某些变量在线程执行期间对相距甚远的两部分代码可见,除了在每个方法调用处将变量作为参数传递(这显然是一种很令人头疼的做法),另一个选择就是使用ThreadLocal静态变量。

ThreadLocal变量是和线程绑定的,而不是和任务绑定。忽视这点,可能会造成一些意想不到的问题。在线程池模式中,一个线程会被多次用来执行不同的任务,比如在servlet中,是采用线程池来为用户请求服务(这里的任务是用户请求):当一个http请求到达时,servlet容器会从线程池中取出一个空闲线程处理请求,处理完毕,再将线程放回线程池,以便可以继续为后续的请求服务。我曾在一次开发过程中,遇到因ThreadLocal使用不当造成的问题。

具体情况是这样的:当用户访问我们的网站服务(服务端是基于servlet)时,需要针对来自某种类型的用户终端的请求进行特殊处理,给出不同的响应。在服务端代码中,很多不同的模块都要对该终端类型进行判断,因此,我使用Filter,在处理请求之前,识别出该终端类型并保存在ThreadLocal静态变量中,以便其它模块直接使用。代码大体如下:

01public class UserAgentFilterimplements Filter {

02public static final ThreadLocal specialUA =newThreadLocal() {

03protected Boolean initialValue() {

04return false;

05}

06};

07public void doFilter(ServletRequest request, ServletResponse response) {

08if (isSpecialUserAgent(request)) {//1//

09specialUA.set(true);

10}

11

12}

13}

这段功能上线后,通过对访问日志的分析发现,对一部分原本不是该类型的终端请求也进行了特殊处理。造成这种现象的原因是:第一次,当一个来自特殊终端的请求到达时,在1处条件判断为真,从而将specialUA的值设置为true,当请求处理结束时,处理该请求的线程被放入线程池。第二次,当一个普通终端的请求到达时,线程池中的一个空闲线程被选中来为该请求服务,选中的线程碰巧是第一次的请求处理线程,而此时在1处条件判断为假,specialUA的值在此次请求中未被设置,但却仍然为true。因为ThreadLocal变量的值是该线程在处理第一次请求时被设置的。

对该问题的解决方法是:在请求处理结束时,线程被放入线程池之前,将specialUA的值设置为默认值(false),或者在每次请求开始时都重新设置该值:

1......

2if (isSpecialUserAgent(request)) {//1//

3specialUA.set(true);

4}else {

5specialUA.set(false);

6}

7......

Thread Local Storage是一种很有用的多线程设计模式,特别是在将单线程程序改为多线程时。Java中的ThreadLocal类是该模式的一个实现。ThreadLocal变量值是和线程绑定的,不是和任务绑定。当ThreadLocal(或Thread Local Storage模式)和线程池模式一起使用时,一定要在任务开始或结束之时,将ThreadLocal变量设置为默认值,以防出现不可预料的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值