早在JDK 1.2
的版本中就提供java.lang.ThreadLocal
,ThreadLocal
为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal
很容易让人望文生义,想当然地认为是一个“
本地线程”
。其实,ThreadLocal
并不是一个Thread
,而是Thread
的局部变量,也许把它命名为ThreadLocalVariable
更容易让人理解一些。
当使用ThreadLocal
维护变量时,ThreadLocal
为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”
所要表达的意思。
线程局部变量并不是Java
的新发明,很多语言(如IBM IBM XL FORTRAN
)在语法层面就提供线程局部变量。在Java
中没有提供在语言级支持,而是变相地通过ThreadLocal
的类提供支持。
所以,在Java
中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java
开发者中得到很好的普及。
ThreadLocal
的接口方法
ThreadLocal
类接口很简单,只有4
个方法,我们先来了解一下:
设置当前线程的线程局部变量的值。
该方法返回当前线程所对应的线程局部变量。
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0
新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
-
protected Object
initialValue()
返回该线程局部变量的初始值,该方法是一个protected
的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1
次调用get()
或set(Object)
时才执行,并且仅执行1
次
。ThreadLocal
中的缺省实现直接返回一个null
。
值得一提的是,在JDK5.0
中,ThreadLocal
已经支持泛型,该类的类名已经变为ThreadLocal<T>
。API
方法 也相应进行了调整,新版本的API
方法分别是void set(T value)
、T get()
以及T initialValue()
。
ThreadLocal
是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal
类中有一个Map
,用于存储每一个线程的变量副本,Map
中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:
代码清单
1 SimpleThreadLocal
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
valueMap.put(Thread.currentThread(), newValue
);①
键为线程对象,值为本线程的变量副本
}
public Object get() {
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);②
返回本线程对应的变量
if (o == null && !valueMap.containsKey(currentThread)) {③
如果在Map
中不存在,放到Map
中保存起来。
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
虽然代码清单9‑3
这个ThreadLocal
实现版本显得比较幼稚,但它和JDK
所提供的ThreadLocal
类在实现思路上是相近的。
一个TheadLocal
实例
下面,我们通过一个具体的实例了解一下ThreadLocal
的具体使用方法。
代码清单
2 SequenceNumber
package com.baobaotao.basic;
public class SequenceNumber {
①
通过匿名内部类覆盖ThreadLocal
的initialValue()
方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>
(){
public Integer initialValue(){
return 0;
}
};
②
获取下一个序列值
public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args)
{
SequenceNumber sn
= new SequenceNumber();
③ 3
个线程共享sn
,各自产生序列号
TestClient t1 = new TestClient(sn
);
TestClient t2 = new TestClient(sn
);
TestClient t3 = new TestClient(sn
);
t1.start();
t2.start();
t3.start();
}
private static class TestClient extends Thread
{
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
public void run()
{
for (int i = 0; i < 3; i++) {④
每个线程打出3
个序列值
System.out.println("thread["+Thread.currentThread().getName()+
"] sn["+sn.getNextNum()
+"]");
}
}
}
}
通常我们通过匿名内部类的方式定义ThreadLocal
的子类,提供初始的变量值,如例子中①
处所示。TestClient
线程产生一组序列号, 在③
处,我们生成3
个TestClient
,它们共享同一个SequenceNumber
实例。运行以上代码,在控制台上输出以下的结果:
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]
考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber
实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal
为每一个线程提供了单独的副本。
Thread
同步机制的比较
ThreadLocal
和线程同步机制相比有什么优势呢?ThreadLocal
和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal
则从另一个角度来解决多线程的并发访问。ThreadLocal
会为每一个线程提供一个独立的变量副本,从而隔离了多个线 程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal
提供了线程安全的共享对象,在编 写多线程代码时,可以把不安全的变量封装进ThreadLocal
。
由于ThreadLocal
中可以持有任何类型的对象,低版本JDK
所提供的get()
返回的是Object
对象,需要强制类型转换。但JDK 5.0
通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal
的使用,代码清单 9 2
就使用了JDK 5.0
新的ThreadLocal<T>
版本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“
以时间换空间”
的方式,而ThreadLocal
采用了“
以空间换时间”
的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
Spring
使用ThreadLocal
解决线程安全问题
我们知道在一般情况下,只有无状态的Bean
才可以在多线程环境下共享,在Spring
中,绝大部分Bean
都可以声明为singleton
作用 域。就是因为Spring
对一些Bean
(如RequestContextHolder
、 TransactionSynchronizationManager
、LocaleContextHolder
等)中非线程安全状态采用 ThreadLocal
进行处理,让它们也成为线程安全的状态,因为有状态的Bean
就可以在多线程中共享了。
一般的Web
应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图所示:
图
1
同一线程贯通三层
这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal
存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。
下面的实例能够体现Spring
对有状态Bean
的改造思路:
代码清单3 TopicDao
:非线程安全
public class TopicDao {
private Connection conn;①
一个非线程安全的变量
public void addTopic(){
Statement stat = conn.createStatement();②
引用非线程安全变量
…
}
}
由于①
处的conn
是成员变量,因为addTopic()
方法是非线程安全的,必须在使用时创建一个新TopicDao
实例(非singleton
)。下面使用ThreadLocal
对conn
这个非线程安全的“
状态”
进行改造:
代码清单4 TopicDao
:线程安全
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
①
使用ThreadLocal
保存Connection
变量
private static ThreadLocal<Connection>
connThreadLocal = new
ThreadLocal<Connection>();
public static Connection getConnection(){
②
如果connThreadLocal
没有本线程对应的Connection
创建一个新的Connection
,
并将其保存到线程本地变量中。
if (connThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
return connThreadLocal.get();③
直接返回线程本地变量
}
}
public void addTopic() {
④
从ThreadLocal
中获取线程对应的Connection
Statement stat = getConnection().createStatement();
}
}
不同的线程在使用TopicDao
时,先判断connThreadLocal.get()
是否是null
,如果是null
,则说明当前线程还没有对 应的Connection
对象,这时创建一个Connection
对象并添加到本地线程变量中;如果不为null
,则说明当前的线程已经拥有了 Connection
对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection
,而不会使用其它线程的 Connection
。因此,这个TopicDao
就可以做到singleton
共享了。
当然,这个例子本身很粗糙,将Connection
的ThreadLocal
直接放在DAO
只能做到本DAO
的多个方法共享Connection
时
不发生线程安全问题,但无法和其它DAO
共用同一个Connection
,要做到同一事务多DAO
共享同一Connection
,必须在一个共同的外部类 使用ThreadLocal
保存Connection
。
小结
ThreadLocal
是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况
下,ThreadLocal
比直接使用synchronized
同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
- 大小: 16.9 KB
分享到:
相关推荐
关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. 关于线程变量ThreadLocal的介绍以及说明. ...
目录SimpleDateFormat诡异bug复现SimpleDateFormat诡异bug字符串日期转Date日期(parse)Date...介绍ThreadLocal使用demoThreadLocal源码探索ThreadLocal注意事项使用ThreadLocal解决SimpleDateFormat线程安全问题总结...
Java单线程ThreadLocal串值问题解决方案主要介绍了Java单线程ThreadLocal串值问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值。 ThreadLocal简介 ThreadLocal是Java中...
下面将详细介绍ThreadLocal的工作原理、使用方法以及在Android中的实际应用。 ### 1. ThreadLocal工作原理 ThreadLocal内部实现了一个HashMap,用于存储每个线程与对应的变量副本之间的映射关系。当我们创建一个新...
以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。
通过以上介绍,我们可以了解到 `ThreadLocal` 在处理多线程环境中提供了独特的数据隔离机制,常用于创建线程安全的工具类实例,或者在每个线程内部保存全局变量。正确使用和管理 `ThreadLocal` 是避免潜在问题的关键...
在本文中,我们将详细介绍Java ThreadLocal的使用案例,并通过一个实际的优化案例,帮助大家理解ThreadLocal的使用。 ThreadLocal的使用场景 在Java多线程编程中,变量的线程安全是一个非常重要的问题。特别是在...
下面我们将详细介绍 Java 中 ThreadLocal 的正确用法。 用法一:在关联数据类中创建 private static ThreadLocal 在 Java 中,ThreadLocal 实例通常是 private static 字段,位于希望与线程关联状态的类中。例如,...
首先是类的介绍。ThreadLocal类提供了线程本地变量。这些变量使每个线程都有自己的一份拷贝。ThreadLocal期望能够管理一个线程的状态,例如用户id或事务id。 ThreadLocal的使用有很多优点,例如可以解决多线程间...
本文主要介绍了ThreadLocal使用案例的分析,希望能够帮助读者更好地理解ThreadLocal的使用。 在本例中,我们需要实现一个需求,即当修改产品价格的时候,需要记录操作日志。为了实现这个需求,我们可以使用...
主要介绍了关于ThreadLocal对request和response的用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
主要介绍了Java ThreadLocal的设计理念与作用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
主要介绍了JAVA开发常用类库UUID、Optional、ThreadLocal、TimerTask、Base64使用方法与实例详解,需要的朋友可以参考下
计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,...
本文主要介绍了Java引用和Threadlocal的知识点,包括Java中的引用类型、Threadlocal的使用等。 Java中的引用类型: Java中有四种引用类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak ...
本文将详细介绍苍穹外卖系统中如何利用`ThreadLocal`来实现在不同层次之间共享当前登录用户的ID。 #### 二、ThreadLocal简介 `ThreadLocal`是一种Java内置的线程局部变量,它为每一个使用该变量的线程都提供了一个...
本文将深入探讨这些知识点,并结合Eureka作为服务注册与发现中心,介绍如何实现服务提供者和消费者。 首先,Spring Boot是基于Spring框架的快速开发工具,它通过内嵌的Servlet容器(如Tomcat或Jetty),实现了“零...