`
dowhathowtodo
  • 浏览: 779568 次
文章分类
社区版块
存档分类
最新评论

Java中ThreadLocal的设计与使用

 
阅读更多

Java 中 ThreadLocal 的设计与使用 早在 Java 1.2 推出之时, Java 平台中就引入了一个新的支持: java.lang.ThreadLocal, 给我们在编写多线程程序时提供了一种新的选择。 使用这个工具类可以很简洁地编写出优美 的多线程程序,虽然 ThreadLocal 非常有用,但是似乎现在了解它、使用它的朋友还不多。

ThreadLocal 是什么

ThreadLocal 并非是一个线程的本地实现版本,它并不是一个 Thread,而是 thread local variable(线程局部变量)。也许把它命名为 ThreadLocalVar 更加合适。其实线程 局部变量(ThreadLocal)的功用非常简单,就是为每一个使用该变量的线程都提供一个变 量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。 从线程的角度看,就好像每一个线程都完全拥有该变量。线程局部变量并不是 Java 的新发 明,在其它的一些语言编译器实现(如 IBM XL FORTRAN)中,它在语言的层次提供了直接 的支持。因为 Java 中没有提供在语言层次的直接支持,而是提供了一个 ThreadLocal 的类 来提供支持,所以,在 Java 中编写线程局部变量的代码相对比较笨拙,这也许是线程局部 变量没有在 Java 中得到很好的普及的一个原因吧。

ThreadLocal 的设计

首先看看 ThreadLocal 的接口:

Object get() ; // 返回当前线程的线程局部变量副本

protected Object initialValue(); // 返回 该线程局部变量的当前线程的初始值

void set(Object value); // 设置当前线程的线程局部变量副本的值

ThreadLocal 有 3 个方法, 其中值得注意的是 initialValue(), 该方法是一个 protected 的方法, 显然是为了子类重写而特意实现的。 该方法返回当前线程在该线程局部变量的初始 值,这个方法是一个延迟调用方法,在一个线程第 1 次调用 get()或者 set(Object)时才执 行,并且仅执行 1 次。

ThreadLocal 中的实现是直接返回一个 null: protected Object initialValue() { return null; } ThreadLocal 是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单, 在 ThreadLocal 类中有一个 Map, 用于存储每一个线程的变量的副本。

比如下面的示例实现:

public class ThreadLocal {
	private Map values = Collections.synchronizedMap(new HashMap());

	public Object get() {
		Thread curThread = Thread.currentThread();
		Object o = values.get(curThread);

		if (o == null && !values.containsKey(curThread)) {
			o = initialValue();
			values.put(curThread, o);
		}
		return o;
	}

	public void set(Object newValue) {
		values.put(Thread.currentThread(), newValue);
	}

	public Object initialValue() {
		return null;
	}
}

当然,这并不是一个工业强度的实现,但 JDK 中的 ThreadLocal 的实现总体思路也类 似于此。 ThreadLocal 的使用 如果希望线程局部变量初始化其它值,那么需要自己实现 ThreadLocal 的子类并重写 该方法, 通常使用一个内部匿名类对 ThreadLocal 进行子类化, 比如下面的例子, SerialNum 类为每一个类分配一个序号

public class SerialNum { 
	// The next serial number to be assigned
	private static int nextSerialNum = 0;
	private static ThreadLocal serialNum = new ThreadLocal() {
		protected synchronized Object initialValue() {
			return new Integer(nextSerialNum++);
		}
	};
	public static int get() {
		return ((Integer) (serialNum.get())).intValue();
	}
}


SerialNum 类的使用将非常地简单,因为 get()方法是 static 的,所以在需要获取当 前线程的序号时,简单地调用: int serial = SerialNum.get(); 即可。 在线程是活动的并且 ThreadLocal 对象是可访问的时,该线程就持有一个到该线程局 部变量副本的隐含引用, 当该线程运行结束后, 该线程拥有的所以线程局部变量的副本都将 失效,并等待垃圾收集器收集。

ThreadLocal 与其它同步机制的比较

ThreadLocal 和其它同步机制相比有什么优势呢?ThreadLocal 和其它所有的同步机制 都是为了解决多线程中的对同一变量的访问冲突, 在普通的同步机制中, 是通过对象加锁来 实现多个线程对同一变量的安全访问的。 这时该变量是多个线程共享的, 使用这种同步机制 需要很细致地分析在什么时候对变量进行读写, 什么时候需要锁定某个对象, 什么时候释放 该对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal 就从另 一个角度来解决多线程的并发访问,ThreadLocal 会为每一个线程维护一个和该线程绑定的 变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码 时,可以把不安全的整个变量封装进 ThreadLocal,或者把该对象的特定于线程的状态封装 进 ThreadLocal。 由于 ThreadLocal 中可以持有任何类型的对象,所以使用 ThreadLocal get 当前线程 的值是需要进行强制类型转换。但随着新的 Java 版本(1.5)将模版的引入,新的支持模版 参数的 ThreadLocal 类将从中受益。 也可以减少强制类型转换, 并将一些错误检查提前到了 编译期,将一定程度地简化 ThreadLocal 的使用。

总结

当然 ThreadLocal 并不能替代同步机制,两者面向的问题领域不同。同步机制是为了 同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而 ThreadLocal 是隔离多个线程的数据共享, 从根本上就不在多个线程之间共享资源 (变量) , 这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则 使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用 ThreadLocal,这将极大 地简化你的程序,使程序更加易读、简洁。
ThreadLocal 的几种误区

最近由于需要用到 ThreadLocal,在网上搜索了一些相关资料,发现对 ThreadLocal 经常 会有下面几种误解:

一、ThreadLocal 是 java 线程的一个实现 ThreadLocal 的确是和 java 线程有关,不过它并不是 java 线程的一个实现,它只是 用来维护本地变量。针对每个线程,提供自己的变量版本,主要是为了避免线程冲突,每个 线程维护自己的版本。彼此独立,修改不会影响到对方。

二、ThreadLocal 是相对于每个 session 的 ThreadLocal 顾名思义,是针对线程。在 java web 编程上,每个用户从开始到会 话结束,都有自己的一个 session 标识。但是 ThreadLocal 并不是在会话层上。其实, Threadlocal 是独立于用户 session 的。它是一种服务器端行为,当服务器每生成一个新的 线程时,就会维护自己的 ThreadLocal。对于这个误解,个人认为应该是开发人员在本地基 于一些应用服务器测试的结果。众所周知,一般的应用服务器都会维护一套线程池,也就是 说,对于每次访问,并不一定就新生成一个线程。而是自己有一个线程缓存池。对于访问, 先从缓存池里面找到已有的线程,如果已经用光,才去新生成新的线程。所以,由于开发人 员自己在测试时,一般只有他自己在测,这样服务器的负担很小,这样导致每次访问可能是 共用同样一个线程,导致会有这样的误解:每个 session 有一个 ThreadLocal

三、ThreadLocal 是相对于每个线程的,用户每次访问会有新的 ThreadLocal 理论上来说, ThreadLocal 是的确是相对于每个线程, 每个线程会有自己的 ThreadLocal。 但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会 接受到同样的线程。因此,在做基于 TheadLocal 时,需要谨慎,避免出现 ThreadLocal 变 量的缓存,导致其他线程访问到本线程变量(这个我们的 ef 项目就遇到过,需特别注意)

四、对每个用户访问,ThreadLocal 可以多用 可以说,ThreadLocal 是一把双刃剑,用得来的话可以起到非常好的效果。但是, ThreadLocal 如果用得不好,就会跟全局变量一样。代码不能重用,不能独立测试。因为,一些本来可以重用的类, 现在依赖于 ThreadLocal 变量。 如果在其他没有 ThreadLocal 场合, 这些类就变得不可用了。

个人觉得 ThreadLocal 用得很好的几个应用场合,值得参考

1、存放当前 session 用户:quake want 的 jert

2、存放一些 context 变量,比如 webwork 的 ActionContext

3、存放 session,比如 Spring hibernate orm 的 session


分享到:
评论

相关推荐

    java 简单的ThreadLocal示例

    java 简单的ThreadLocal示例

    Java ThreadLocal的设计理念与作用

    主要介绍了Java ThreadLocal的设计理念与作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    设计模式及ThreadLocal资料

    设计模式及ThreadLocal详细讲解资料,想要学习java或者提升自己技术的同学可以下载观看

    Java多线程编程中ThreadLocal类的用法及深入

    早在 JDK 1.2 的时代,java.lang.ThreadLocal 就诞生了,它是为了解决多线程并发问题而设计的,只不过设计得有些难用,所以至今没有得到广泛使用。其实它还是挺有用的,不相信的话,我们一起来看看这个例子吧。 一个...

    Java高级程序设计-多线程(二).pptx

    掌握同步代码块的使用 掌握同步方法的使用 理解线程死锁 掌握 ThreadLocal 类的使用 使用多线程模拟猴子采花 使用同步方法模拟购票 使用多线程模拟购物订单生成 使用 ThreadLocal 类模拟银行取款 Java高级程序设计-...

    实战Java高并发程序设计(第2版)PPT模板.pptx

    2.7线程安全的概念与关键字synchronized 2.8程序中的幽灵:隐蔽的错误 2java并行程序基础 实战Java高并发程序设计(第2版)PPT模板全文共25页,当前为第6页。 3jdk并发包 03 实战Java高并发程序设计(第2版)PPT...

    Java并发编程原理与实战

    Java中的阻塞队列原理与使用.mp4 实战:简单实现消息队列.mp4 并发容器ConcurrentHashMap原理与使用.mp4 线程池的原理与使用.mp4 Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二)....

    java事务 - 模板设计模式

    Template模板设计模式改造threadlocal控制事务

    Java并发编程实战

    3.3.3 ThreadLocal类 3.4 不变性 3.4.1 Final域 3.4.2 示例:使用Volatile类型来发布不可变对象 3.5 安全发布 3.5.1 不正确的发布:正确的对象被破坏 3.5.2 不可变对象与初始化安全性 3.5.3 安全发布的常用...

    java面试题目与技巧1

    基于MVC的.java.web设计与开发.pdf │ 学习Struts提供的和Form相关标签.txt │ 日企编码规范.doc │ 电信盈科面试题.pdf │ 速算.txt │ 面试题URL.txt │ ├─Javascript │ │ javascript资料(源码,教材,ppt)....

    疯狂JAVA讲义

    学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件的位置,也需要指定目标文件的文件名,这里使用javac编译Java程序时怎么不需要指定目标文件的文件名呢? 13 1.5.3 运行Java程序 14 1.5.4 根据...

    java设计模式视频教程-百度网盘地址.txt

    单例模式、工厂模式、装饰者模式、ThreadLocal设计模式、代理模式等

    Java并发程序设计教程

    1、使用线程的经验:设置名称、响应中断、使用ThreadLocal 2、Executor :ExecutorService和Future ☆☆☆ 3、阻塞队列: put和take、offer和poll、drainTo 4、线程间的协调手段:lock、condition、wait、notify、...

    经典JAVA.EE企业应用实战.基于WEBLOGIC_JBOSS的JSF_EJB3_JPA整合开发.pdf

    8.7 在Session Bean中使用事务 327 8.7.1 容器管理事务 327 8.7.2 Bean管理事务 330 8.8 拦截器 332 8.9 依赖注入 335 8.9.1 EJB注入 336 8.9.2 资源注入 339 8.10 配置EJB引用 340 8.11 使用计时器进行任务调度 342...

    Java常见面试题208道.docx

    118.在 hibernate 中使用 Integer 和 int 做映射有什么区别? 119.hibernate 是如何工作的? 120.get()和 load()的区别? 121.说一下 hibernate 的缓存机制? 122.hibernate 对象有哪些状态? 123.在 hibernate 中 ...

    阿里Java并发程序设计教程

    1、使用线程的经验:设置名称、响应中断、使用ThreadLocal 2、Executor :ExecutorService和Future ☆ ☆ ☆ 3、阻塞队列 : put和take、offer和poll、drainTo 4、线程间的协调手段:lock、condition、wait、notify、...

    java面试题及技巧4

    基于MVC的.java.web设计与开发.pdf │ 学习Struts提供的和Form相关标签.txt │ 日企编码规范.doc │ 电信盈科面试题.pdf │ 速算.txt │ 面试题URL.txt │ ├─Javascript │ │ javascript资料(源码,教材,ppt)....

    龙果 java并发编程原理实战

    第49节Java中的阻塞队列原理与使用00:26:18分钟 | 第50节实战:简单实现消息队列00:11:07分钟 | 第51节并发容器ConcurrentHashMap原理与使用00:38:22分钟 | 第52节线程池的原理与使用00:42:49分钟 | 第53节...

Global site tag (gtag.js) - Google Analytics