Java并发-以空间换时间之ThreadLocal
多线程同时对资源进行访问,为了线程安全,最简单的做法就是使用synchronized关键字,这是一种时间换空间的做法。
在注重性能的场合,多线程竞争问题显得愈发突出。当请求时间变长,用户体验变差,流失率增高。
有没有办法解决这个问题呢?Java提供了一种以空间换时间的的容器,它就是ThreadLocal
。
# ThreadLocal
介绍
ThreadLocal
为每个线程维护一份独立的变量副本,它的底层实现是基于Map
来存储。
ThreadLocal
有4个方法:
set(Object value)
设置当前线程的线程局部变量的值get()
返回当前线程所对应的线程局部变量remove()
删除当前线程所对应的线程局部变量initialValue()
返回线程局部变量的初始值
对于我们以数据库连接举例,它本身是非线程安全的:
public class TopicDao {
private Connection connection;
public void addTopic() throws SQLException {
Statement statement = connection.createStatement();
...
}
}
传统方式,要把他变成线程安全,必须使用锁。
ThreadLocal
的方式:
public class TopicDao {
private ThreadLocal<Connection> connection = new ThreadLocal<>(){
@Override
protected Connection initialValue() {
return new MysqlConnection();
}
};
public void addTopic() throws SQLException {
Statement statement = connection.get().createStatement();
...
}
}
以这种方式运行,程序会为每个线程单独分配一个连接,这样在单个线程中就不存在对于Connection
的竞争了。
# 其他典型应用
ThreadLocal
的应用不只在于“以空间换时间”,它还有非常广阔的应用。比如在Spring中统计url请求的时间,我们用到拦截器:
public class UseTimeInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final ThreadLocal<Long> TIME_THREAD_LOACL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
TIME_THREAD_LOACL.set(System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
long time = System.currentTimeMillis() - TIME_THREAD_LOACL.get();
logger.info("{} 访问时间为{}ms", request.getRequestURL());
TIME_THREAD_LOACL.remove();
}
}
# Spring
事务中对于ThreadLocal
的应用
在Spring
的事务管理中,就使用到了ThreadLocal
,从而解决了JDBC Connection
的线程安全问题。
在数据源获取工具org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
方法中存在这样一段代码:
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
...
return con;
}
打开TransactionSynchronizationManager
,里面使用到了多个ThreadLocal
:
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
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");
...
}
Spring
正是通过对ThreadLocal
的应用,实现了对数据库连接无感的线程安全处理。
# 总结
在现实工作中,我们常常会遇到很多问题。有的问题比较简单,有的问题比较复杂,而对于复杂问题的处理往往最能体现一个工程师的价值。
我个人解决复杂问题的心得体会是:平常多学习、多积累。
可以想象当我们不知道ThreadLocal
这样的工具时,遇到多线程竞争问题,思维还停留在加锁层面。而脑海中有了更多的工具箱时,一些情况下解决问题的思路也会随之打开!
祝你变得更强!