编写线程安全代码的关键是管理程序中的共享可变状态,除了通过synchronized加锁机制防止多个线程同时访问同一段数据外,还有一种方法就是通过ThreadLocal消除数据的共享,ThreadLocal会为各自线程创建相应的变量副本(线程局部变量),每个副本都由各自线程管理,这样就避免了对共享资源的访问冲突,也减少了同步时的性能消耗。我们来看一段示例:
class Sequence implements Runnable {
private final int tid;
public Sequence(int tid) {
this.tid = tid;
}
public void run() {
while (!Thread.currentThread().isInterrupted()&&VarHolder.get()<6) {
VarHolder.increment();
System.out.println(this);
//提示调度器,让相同优先级的线程获得运行的机会,方便重现竞争条件的情景
Thread.yield();
}
}
public String toString() {
return "tid" + tid + ": " + VarHolder.get();
}
}
public class VarHolder {
private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
protected synchronized Integer initialValue() {
return 0;
}
};
public static void increment() {
// set()设置当前线程的局部线程变量的值
value.set(value.get() + 1);
}
public static int get() {
//get()返回当前线程对应的线程局部变量
return value.get();
}
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++){
exec.execute(new Sequence(i));
}
Thread.sleep(2000);
exec.shutdownNow();
}
}
运行结果:
tid3: 1
tid0: 1
tid1: 1
tid4: 1
tid2: 1
tid3: 2
tid2: 2
tid3: 3
tid4: 2
tid3: 4
tid4: 3
tid1: 2
tid0: 2
tid3: 5
tid2: 3
tid1: 3
tid0: 3
tid4: 4
tid0: 4
tid1: 4
tid2: 4
tid3: 6
tid2: 5
tid1: 5
tid0: 5
tid4: 5
tid2: 6
tid1: 6
tid0: 6
tid4: 6
每个线程都按序输出1-6,执行正常。
ThreadLocal有各种应用场景,比如在Spring中的事务管理模块,ThreadLocal就有精彩的表现。
基于软件工程中的模块化设计原则,我们会将业务操作与数据访问拆分开来,将业务逻辑放在service层,将数据访问模块放在Dao层,service层通过Dao层进行数据访问,而事务管理是放在service层的,这样拆分,提高了模块的重用性,一个service有可能调用若干个dao,而要让多个dao的访问在同一个事务下,则他们必须使用同一个connection,因为性能和并发的要求,connection不会是全局变量,于是我们通过传参的方式把当前connection传到相应的调用的dao方法中。
public void serviceA(){
Connection conn = transactionManager.doBegin();
dao1.doX(conn);
dao2.doY(conn);
transactionManager.doEnd(conn);
}
事务管理代码依然和数据访问层紧密耦合,无法实现重用,假如现在需要的不是JDBC的connection,而是其他资源,比如是hibernate的session,那这一切是不是都要重新调整了?
理想中的调用应该是这样的:
public void serviceA(){
Connection conn= transactionManager.doBegin();
dao1.doX();
dao2.doY();
transactionManager.doEnd(conn);
}
事务管理层和数据访问服务之间不能直接耦合,在事务开始阶段,将connection与当前线程绑定,数据访问时,从当前线程获取绑定的connection进行操作,等事务提交或回滚后,解除绑定。Spring有两个主要类实现这个功能,AbstractPlatformTransactionManager和TransactionSynchronizationManager,而核心机制就是使用了ThreadLocal。
AbstractPlatformTransactionManager针对不同的数据访问技术,有着不同的实现类,如DataSourceTransactionManager和HibernateTransactionManager,以前者为例:
在doBegin方法中,他将资源(connection)与当前线程绑定起来:
bindResource方法源码如下:
那resources又是神马呢,聪明的你或许已经猜到了,正是我们开始讲的ThreadLocal。
再后续的数据访问中,他就是从当前线程中获取资源(connection)进行操作的。可以看JdbcTemplate的execute()方法源码验证下:
他通过DataSourceUtils获取connection,这个connection又是怎么获得的呢?
他调用TransactionSynchronizationManager的getResource方法获取资源,判断当前线程下是否有绑定的connection,如果没有,则重新从dataSource获取。
Spring事务管理通过使用ThreadLocal,解除了事务管理模块与数据访问层的紧密耦合,提高了模块的可重用性,也保证了多线程环境下的对connection资源的有效管理,实现了线程安全。而要将事务管理代码从整个业务逻辑中抽离出来,提供系统性的服务,还有许多事情要做。Spring 正是通过aop机制解决这个问题的,这个我们下次再讲。