- 浏览: 18995 次
最新评论
Java.lang.ThreadLocal类
- 博客分类:
- 技术杂绘
Java.lang.ThreadLocal类
2010年12月09日
一、概述
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实际的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了"以时间换空间"的方式,而ThreadLocal采用了"以空间换时间"的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
二、API说明
该类提供了线程局部变量。这些变量不同于它们的普通对应物,因为访问一个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的私有静态字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如,在下面的类中,私有静态 ThreadLocal 实例(serialNum)为调用该类的静态 SerialNum.get() 方法的每个线程维护了一个"序列号",该方法将返回当前线程的序列号。(线程的序列号是在第一次调用 SerialNum.get() 时分配的,并在后续调用中不会更改。)
ThreadLocal()
创建一个线程本地变量。
T get()
返回此线程局部变量的当前线程副本中的值,如果这是线程第一次调用该方法,则创建并初始化此副本。
protected T initialValue()
返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。
若该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal 创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。
void remove()
移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。
void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
在程序中一般都重写initialValue方法,以给定一个特定的初始值。
三、典型实例
1、Hiberante的Session 工具类HibernateUtil
这个类是Hibernate官方文档中HibernateUtil类,用于session管理。
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; //定义SessionFactory static { try { // 通过默认配置文件hibernate.cfg.xml创建SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("初始化SessionFactory失败!", ex); throw new ExceptionInInitializerError(ex); } } //创建线程局部变量session,用来保存Hibernate的Session public static final ThreadLocal session = new ThreadLocal(); /** * 获取当前线程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // 如果Session还没有打开,则新开一个Session if (s == null) { s = sessionFactory.openSession(); session.set(s); //将新开的Session保存到线程局部变量中 } return s; } public static void closeSession() throws HibernateException { //获取线程局部变量,并强制转换为Session类型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); } }
在这个类中,由于没有重写ThreadLocal的initialValue()方法,则首次创建线程局部变量session其初始值为null,第一次调用currentSession()的时候,线程局部变量的get()方法也为null。因此,对session做了判断,如果为null,则新开一个Session,并保存到线程局部变量session中,这一步非常的关键,这也是"public static final ThreadLocal session = new ThreadLocal()"所创建对象session能强制转换为Hibernate Session对象的原因。
2、另外一个实例
创建一个Bean,通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
public class Student { private int age = 0; //年龄 public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } public class ThreadLocalDemo implements Runnable { //创建线程局部变量studentLocal,在后面你会发现用来保存Student对象 private final static ThreadLocal studentLocal = new ThreadLocal(); public static void main(String[] agrs) { ThreadLocalDemo td = new ThreadLocalDemo(); Thread t1 = new Thread(td, "a"); Thread t2 = new Thread(td, "b"); t1.start(); t2.start(); } public void run() { accessStudent(); } /** * 示例业务方法,用来测试 */ public void accessStudent() { //获取当前线程的名字 String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running!"); //产生一个随机数并打印 Random random = new Random(); int age = random.nextInt(100); System.out.println("thread " + currentThreadName + " set age to:" + age); //获取一个Student对象,并将随机数年龄插入到对象属性中 Student student = getStudent(); // 事实上这里直接new student()也是没有线程安全问题的,测试结果也是一样。局部变量不存在线程安全问题。 student.setAge(age); System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge()); try { Thread.sleep(500); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge()); } protected Student getStudent() { //获取本地线程变量并强制转换为Student类型 Student student = (Student) studentLocal.get(); //线程首次执行此方法的时候,studentLocal.get()肯定为null if (student == null) { //创建一个Student对象,并保存到本地线程变量studentLocal中 student = new Student(); studentLocal.set(student); } return student; } }
运行结果:
a is running!
thread a set age to:76
b is running!
thread b set age to:27
thread a first read age is:76
thread b first read age is:27
thread a second read age is:76
thread b second read age is:27
可以看到a、b两个线程age在不同时刻打印的值是完全相同的。这个程序通过妙用ThreadLocal,既实现多线程并发,游兼顾数据的安全性。(这里不用ThreadLocal也是没问题的。直接new student()测试结果也是一样。局部变量不存在线程安全问题。)
四、总结
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
五、ThreadLocal使用的一般步骤
1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型;也可以一开始就使用threadLocal的set方法设置好。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
发表评论
-
.net 线程概述
2012-01-20 09:02 501.net 线程概述 2011年06月23日 概论 多线 ... -
Borland 基础与应用开发课程认证试题整理集
2012-01-20 09:02 603Borland 基础与应用开发课程认证试题整理集 2010年 ... -
windows x64 vista以上系统代码完整性校验分析
2012-01-20 09:02 1644windows x64 vista以上系统代码完整性校验分析 ... -
C1X系列: 多线程(N1494)
2012-01-20 09:02 716C1X系列: 多线程(N1494) 2010年08月03日 ... -
VBS整人代码 很多 测试把我给整安逸了
2012-01-19 14:06 2254VBS整人代码 很多 测试把我给整安逸了 2010年08月2 ... -
利用VBS让QQ永远在线
2012-01-19 14:06 557利用VBS让QQ永远在线 201 ... -
VBS Runas 自动明文的输入密码
2012-01-19 14:06 699VBS Runas 自动明文的输入密码 2010年06月23 ... -
VBS运用之妙用SendKeys
2012-01-19 14:06 905VBS运用之妙用SendKeys 2010年11月12日 ... -
了解VBE!VBS教程!
2012-01-19 14:06 685了解VBE!VBS教程! 2011年09 ... -
使用Windbg排除蓝屏故障(已发表)
2012-01-17 03:56 634使用Windbg排除蓝屏故障(已发表) 2011年09月28 ... -
系统的一些解决方法
2012-01-17 03:56 605系统的一些解决方法 20 ... -
2011-11-1
2012-01-17 03:56 4662011-11-1 2011年11月01日 第一篇:一 ... -
Linux下FrameBuffer直接写屏
2012-01-17 03:56 983Linux下FrameBuffer直接写屏 2011年09月 ... -
exe文件不能运行的解决方法
2012-01-17 03:56 1034exe文件不能运行的解决 ... -
Socket基础
2012-01-16 02:45 543Socket基础 2010年04月02日 Socket基 ... -
一个类似ifconfig功能的程序代码--C语言(ZZ)
2012-01-16 02:44 953一个类似ifconfig功能的程序代码--C语言(ZZ) 2 ... -
tcp简单实现C/S模式程序,测试成功
2012-01-16 02:44 1095tcp简单实现C/S模式程序,测试成功 2010年03月25 ... -
简单的C#Socket编程
2012-01-16 02:44 552简单的C#Socket编程 2009年06月23日 us ... -
用C#做远程监控程序
2012-01-16 02:44 3150用C#做远程监控程序 2009年12月05日 最近在做的 ...
相关推荐
入研究java.lang.ThreadLocal类.docx
深入研究java.lang.ThreadLocal类。ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。
解决mongo数据插入时 报错问题 mogodb插入数据时报错Can't find a codec for class java.math.BigDecimal
remove是java.lang.ThreadLocal类的方法,但是jdk1.4中,却没有这个方法的实现,jdk1.5中有这个方法,因此是没问题的; 本jar包经过改造,适用于JDK1.4,需要第三方JAR包的支持(commons-beanutils-1.8.2.jar,commons-...
remove是java.lang.ThreadLocal类的方法,但是jdk1.4中,却没有这个方法的实现,jdk1.5中有这个方法,因此是没问题的; 本jar包经过改造,适用于JDK1.4,需要第三方JAR包的支持(commons-beanutils-1.8.2.jar,commons...
线程本地 此实现是一项实验,旨在与现有实现相比提高正确性、性能、内存使用和自由度。
另一个值得考虑的是多线程java.lang.ThreadLocal的实例。偷懒的做法是通过Java本身API实现单一实例,当然你也可以确保每一个线程都有自己的一个实例对象。 虽然Java没有提供一个很好的方法来管理java.util.Random的...
早在 JDK 1.2 的时代,java.lang.ThreadLocal 就诞生了,它是为了解决多线程并发问题而设计的,只不过设计得有些难用,所以至今没有得到广泛使用。其实它还是挺有用的,不相信的话,我们一起来看看这个例子吧。 一个...
2.3 线程本地存储(Java.lang.ThreadLocal) 15 2.4 线程阻塞 17 2.4.1 调用sleep(millisecond)使任务进入休眠状态 17 2.4.2 等待输出与输入 17 2.4.3 对象锁不可用 17 2.4.4 通过wait()使线程挂起。 17 2.5 线程...
java.lang.ref和jdk.internal.ref下的各种引用:软引用/弱引用/虚引用 Unsafe的实现(JDK9之后有两个同名类,一个引用了另一个,建议放在一起阅读) java.util.stream下的流式编程的实现(很难) Thread和...
java.lang Object 1 String 1 AbstractStringBuilder 1 StringBuffer 1 StringBuilder 1 Boolean 2 Byte 2 Double 2 Float 2 Integer 2 Long 2 Short 2 Thread 2 ThreadLocal 2 Enum 3 Throwable 3 Error 3 ...
展示了如何与org.springframework.web.server.WebFilter reactor.util.context.Context进行交互,就像java.lang.ThreadLocal一样。 org.springframework.cloud.spring-cloud-starter-sleuth有一个很好的解决方案! ...
学生提问:为什么我创建Java对象时从未感觉到java.lang.Object的构造器被调用过? 150 5.7 多态 151 5.7.1 多态性 151 5.7.2 引用变量的强制类型转换 152 5.7.3 instanceof运算符 154 5.8 继承与组合 154 ...
ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量 8.3.5. ...
ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量...
ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量...
ThreadLocal目标源 7.11. 定义新的Advice类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.2.1. Mock对象 8.2.2. 单元测试支持类 8.3. 集成测试 8.3.1. 概览 8.3.2. 使用哪个支持框架 8.3.3. ...
ThreadLocal目标源 7.11. 定义新的Advice类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.2.1. Mock对象 8.2.2. 单元测试支持类 8.3. 集成测试 8.3.1. 概览 8.3.2. 使用哪个支持框架 8.3.3. ...
【JVM】能不能自己写个类叫java.lang.System? 57 【JVM】类的加载过程 58 【JVM】类的初始化 58 类什么时候才被初始化: 58 类的初始化步骤: 59 【*JVM】什么是JVM线程死锁?JVM线程死锁,你该如何判断是因为什么...
7.9.1 java.lang.instrument包的工作原理 7.9.2 如何向JVM中注册转换器 7.9.3 使用JVM启动参数注册转换器的问题 7.10 使用LTW织入切面 7.10.1 Spring的LoadTimeWeaver 7.10.2 使用LTW织入一个切面 7.10.3 在Tomcat下...