??xml version="1.0" encoding="utf-8" standalone="yes"?>
我们知道Spring通过各种DAO模板c降低了开发者用各U数据持久技术的隑ֺ。这些模板类都是U程安全的,也就是说Q多个DAO可以复用同一个模板实例而不会发生冲H?/p>
我们使用模板c访问底层数据,Ҏ持久化技术的不同Q模板类需要绑定数据连接或会话的资源。但q些资源本n是非U程安全的,也就是说它们不能在同一时刻被多个线E共享?/p>
虽然模板c通过资源池获取数据连接或会话Q但资源池本w解决的是数据连接或会话的缓存问题,q数据q接或会话的U程安全问题?/p>
按照传统l验Q如果某个对象是非线E安全的Q在多线E环境下Q对对象的访问必采用synchronizedq行U程同步。但Spring的DAO模板cdƈ未采用线E同步机Ӟ因ؓU程同步限制了ƈ发访问,会带来很大的性能损失?/p>
此外Q通过代码同步解决性能安全问题挑战性很大,可能会增强好几倍的实现隑ֺ。那模板cȝ竟Ԓ丈何U魔法神功,可以在无需同步的情况下化解线E安全的N呢?{案是ThreadLocalQ?/p>
ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、Q务调度、AOP{模块都出现了它们的w媄Qv着举轻重的作用。要想了解Spring事务理的底层技术,ThreadLocal是必L克的山头堡垒?/p>
ThreadLocal是什?/strong>
早在JDK 1.2的版本中提供java.lang.ThreadLocalQThreadLocal军_U程E序的ƈ发问题提供了一U新的思\。用这个工L可以很简z地~写Z的多线E程序?/p>
ThreadLocal很容易让人望文生义,惛_然地认ؓ是一?#8220;本地U程”。其实,ThreadLocalq不是一个ThreadQ而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些?/p>
当用ThreadLocall护变量ӞThreadLocal为每个用该变量的线E提供独立的变量副本Q所以每一个线E都可以独立地改变自q副本Q而不会媄响其它线E所对应的副本?/p>
从线E的角度看,目标变量p是线E的本地变量Q这也是cd?#8220;Local”所要表辄意思?/p>
U程局部变量ƈ不是Java的新发明Q很多语aQ如IBM IBM XL FORTRANQ在语法层面提供线E局部变量。在Java中没有提供在语言U支持,而是变相地通过ThreadLocal的类提供支持?/p>
所以,在Java中编写线E局部变量的代码相对来说要笨拙一些,因此造成U程局部变量没有在Java开发者中得到很好的普及?/p>
ThreadLocal的接口方?/p>
ThreadLocalcL口很单,只有4个方法,我们先来了解一下:
讄当前U程的线E局部变量的倹{?/p>
该方法返回当前线E所对应的线E局部变量?/p>
当前线E局部变量的值删除,目的是ؓ了减内存的占用Q该Ҏ是JDK 5.0新增的方法。需要指出的是,当线E结束后Q对应该U程的局部变量将自动被垃圑֛Ӟ所以显式调用该Ҏ清除U程的局部变量ƈ不是必须的操作,但它可以加快内存回收的速度?/p>
q回该线E局部变量的初始|该方法是一个protected的方法,昄是ؓ了让子类覆盖而设计的。这个方法是一个gq调用方法,在线E第1ơ调用get()或set(Object)时才执行Qƈ且仅执行1ơ。ThreadLocal中的~省实现直接q回一个null?/p>
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型Q该cȝcd已经变ؓThreadLocal<T>。APIҎ 也相应进行了调整Q新版本的APIҎ分别是void set(T value)、T get()以及T initialValue()?/p>
ThreadLocal是如何做Cؓ每一个线E维护变量的副本的呢Q其实实现的思\很简单:在ThreadLocalcM有一个MapQ用于存储每一个线E的变量副本QMap中元素的键ؓU程对象Q而值对应线E的变量副本。我们自己就可以提供一个简单的实现版本Q?/p>
代码清单1 SimpleThreadLocal
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
valueMap.put(Thread.currentThread(), newValue);①键为线E对象,gؓ本线E的变量副本
}
public Object get() {
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);②返回本U程对应的变?/strong>
if (o == null && !valueMap.containsKey(currentThread)) {③如果在Map中不存在Q放到Map
中保存v来?/strong>
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
虽然代码清单9?q个ThreadLocal实现版本昑־比较q稚Q但它和JDK所提供的ThreadLocalcd实现思\上是相近的?/p>
一个TheadLocal实例
下面Q我们通过一个具体的实例了解一下ThreadLocal的具体用方法?/p>
代码清单2 SequenceNumber
package com.baobaotao.basic;
public class SequenceNumber {
?/strong>通过匿名内部c覆?/strong>ThreadLocal?/strong>initialValue()ҎQ指定初始?/strong>
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
?/strong>获取下一个序列?/strong>
public int getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args)
{
SequenceNumber sn = new SequenceNumber();
?/strong> 3个线E共?/strong>snQ各自生序列号
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++) {④每个线E打?/strong>3个序列?/strong>
System.out.println("thread["+Thread.currentThread().getName()+
"] sn["+sn.getNextNum()+"]");
}
}
}
}
通常我们通过匿名内部cȝ方式定义ThreadLocal的子c,提供初始的变量|如例子中①处所C。TestClientU程产生一l序列号Q? 在③处,我们生成3个TestClientQ它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输Z下的l果Q?/p>
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]
考察输出的结果信息,我们发现每个U程所产生的序可焉׃n同一个SequenceNumber实例Q但它们q没有发生相互干扰的情况Q而是各自产生独立的序列号Q这是因为我们通过ThreadLocal为每一个线E提供了单独的副本?/p>
Thread同步机制的比?/p>
ThreadLocal和线E同步机制相比有什么优势呢QThreadLocal和线E同步机刉是ؓ了解军_U程中相同变量的讉K冲突问题?/p>
在同步机制中Q通过对象的锁机制保证同一旉只有一个线E访问变量。这时该变量是多个线E共享的Q用同步机制要求程序慎密地分析什么时候对变量q行dQ什么时候需要锁定某个对象,什么时候释攑֯象锁{繁杂的问题Q程序设计和~写隑ֺ相对较大?/p>
而ThreadLocal则从另一个角度来解决多线E的q发讉K。ThreadLocal会ؓ每一个线E提供一个独立的变量副本Q从而隔M多个U? E对数据的访问冲H。因为每一个线E都拥有自己的变量副本,从而也没有必要对该变量进行同步了。ThreadLocal提供了线E安全的׃n对象Q在~? 写多U程代码Ӟ可以把不安全的变量封装进ThreadLocal?/p>
׃ThreadLocal中可以持有Q何类型的对象Q低版本JDK所提供的get()q回的是Object对象Q需要强制类型{换。但JDK 5.0通过泛型很好的解决了q个问题Q在一定程度地化ThreadLocal的用,代码清单 9 2׃用了JDK 5.0新的ThreadLocal<T>版本?/p>
概括h_对于多线E资源共享的问题Q同步机刉用了“以时间换I间”的方式,而ThreadLocal采用?#8220;以空间换旉”的方式。前者仅提供一份变量,让不同的U程排队讉KQ而后者ؓ每一个线E都提供了一份变量,因此可以同时讉K而互不媄响?/p>
Spring使用ThreadLocal解决U程安全问题
我们知道在一般情况下Q只有无状态的Bean才可以在多线E环境下׃nQ在Spring中,l大部分Bean都可以声明ؓsingleton作用 域。就是因为Spring对一些BeanQ如RequestContextHolder? TransactionSynchronizationManager、LocaleContextHolder{)中非U程安全状态采? ThreadLocalq行处理Q让它们也成为线E安全的状态,因ؓ有状态的Bean可以在多线E中׃n了?/p>
一般的Web应用划分为展现层、服务层和持久层三个层次Q在不同的层中编写对应的逻辑Q下层通过接口向上层开攑֊能调用。在一般情况下Q从接收h到返回响应所l过的所有程序调用都同属于一个线E,如图9?所C:
?/a>1同一U程贯通三?/p>
q样你就可以Ҏ需要,一些非U程安全的变量以ThreadLocal存放Q在同一ơ请求响应的调用U程中,所有关联的对象引用到的都是同一个变量?/p>
下面的实例能够体现SpringҎ状态Bean的改造思\Q?/p>
代码清单3 TopicDaoQ非U程安全 public class TopicDao { private Connection conn;?/strong>一个非U程安全的变?/strong> public void addTopic(){ Statement stat = conn.createStatement();?/strong>引用非线E安全变?/strong> … } } ׃①处的conn是成员变量,因ؓaddTopic()Ҏ是非U程安全的,必须在用时创徏一个新TopicDao实例Q非singletonQ。下面用ThreadLocal对connq个非线E安全的“状?#8221;q行攚w: 代码清单4 TopicDaoQ线E安?/p>
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没有本线E对应的Connection创徏一个新的ConnectionQ?/strong> q将其保存到U程本地变量中?/strong> if (connThreadLocal.get() == null) { Connection conn = ConnectionManager.getConnection(); connThreadLocal.set(conn); return conn; }else{ return connThreadLocal.get();③直接返回线E本地变?/strong> } } public void addTopic() { ④从ThreadLocal中获取线E对应的Connection Statement stat = getConnection().createStatement(); } } 不同的线E在使用TopicDaoӞ先判断connThreadLocal.get()是否是nullQ如果是nullQ则说明当前U程q没有对
应的Connection对象Q这时创Z个Connection对象q添加到本地U程变量中;如果不ؓnullQ则说明当前的线E已l拥有了
Connection对象Q直接用就可以了。这P׃证了不同的线E用线E相关的ConnectionQ而不会用其它线E的
Connection。因此,q个TopicDao可以做到singleton׃n了?/p>
当然Q这个例子本w很_糙Q将Connection的ThreadLocal直接攑֜DAO只能做到本DAO的多个方法共享Connection?
不发生线E安全问题,但无法和其它DAOq同一个ConnectionQ要做到同一事务多DAO׃n同一ConnectionQ必d一个共同的外部c?
使用ThreadLocal保存Connection。但q个实例基本上说明了SpringҎ状态类U程安全化的解决思\?/p>
结 ThreadLocal是解决线E安全问题一个很好的思\Q它通过为每个线E提供一个独立的变量副本解决了变量ƈ发访问的冲突问题。在很多情况下,
ThreadLocal比直接用synchronized同步机制解决U程安全问题更简单,更方便,且结果程序拥有更高的q发性?/p>