ThreadLocal线程本地存储

什么是ThreadLocal?首先要说明的一点是ThreadLocal并不是一个Thread,而是Thread的局部变量。在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。下面我们就来看看ThreadLocal的初步内容:

多线程安全性解决方案

①进行同步控制synchronized效率降低 并发变同步(串行) ,lock(串行)控制灵活

②使用ThreadLocal 本地线程 每个线程一个变量副本(各不相干)

两种线程安全方案的差异

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队 访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响,ThreadLocal占用内存较大,但是速度快,而线程同步相对内存占用小,但是速度慢。如果在内存比较充足的情况,对并发部分的执行效率要求很高的话,那么就是ThreadLocal登场的时候了。一般情况下用同步机制还是居多的。

理解ThreadLocal的原理

每个ThreadLocal对象内部有个ThreadLocalMap,当线程访问ThreadLocal对象时,会在线程内部的ThreadLocalMap新建一个Entry,这样的话每个线程都有一个对象的副本,保证了并发场景下的线程安全。我理解ThreadLocal的使用场景是某些对象在多线程并发访问时可能出现问题,比如使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了,我们就可以用ThreadLocal<SimpleDataFormat>来解决并发修改的问题。另一种场景是Spring事务,事务是和线程绑定起来的,Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离,代码如下所示:

public abstract class TransactionSynchronizationManager {

//线程绑定的资源,比如DataSourceTransactionManager绑定是的某个数据源的一个Connection,在整个

//事务执行过程中 都使用同一个Jdbc Connection

private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

//事务注册的事务同步器

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");

//事务名称

private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");

//事务只读属性

private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");

//事务隔离级别

private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");

//事务同步开启

private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active"); }

不是说一遇到并发场景就用ThreadLocal来解决,我们还可以用synchronized或者锁来实现线程安全,ThreadLocal使用不当时会引起内存泄露的问题

ThreadLocal就是变量在不同线程上的副本,不同线程不共享,所以对变量改动时就不需要考虑线程间同步的问题了

ThreadLocal在web应用开发中是一种很常见的技巧,当web端采用无状态写法时(比如stateless session bean和spring默认的singleton),就可以考虑把一些变量放在ThreadLocal中

举个简单例子,你有两个方法A和B都要用到变量userId,又不想传来传去,一个很自然的想法就是把userId设为成员变量,但是在无状态时,这样做就很可能有问题,因为多个request在同时使用同一个instance,userId在不同request下值是不一样的,就会出现逻辑错误
但由于同一个request下一般都是处于同一个线程,如果放在ThreadLocal的话,这个变量就被各个方法共享了,而又不影响其他request,这种情况下,你可以简单把它理解为是一种没有副作用的成员变量

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中, 绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、 TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用 ThreadLocal进行处理,让它们也成为线程安全的状态。

猜你喜欢

转载自my.oschina.net/u/1054538/blog/1624001