轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

      • Java并发-线程基础与synchronized关键字
      • Java并发-重入锁ReetrantLock的使用
      • Java并发-信号量Semaphore
      • Java并发-读写锁ReadWriteLock
      • Java并发-倒计时器CountDownLatch
      • Java并发-栅栏CyclicBarrier
      • Java并发-LockSupport线程阻塞工具类
      • Java并发-线程池ThreadPoolExecutor
      • Java并发-阻塞队列BlockingQueue
      • Java并发-以空间换时间之ThreadLocal
        • ThreadLocal介绍
          • 其他典型应用
        • Spring事务中对于ThreadLocal的应用
        • 总结
      • Java并发-无锁策略CAS与atomic包
      • Java并发-JDK并发容器
      • Java并发-异步调用结果之Future和CompletableFuture
      • Java并发-Fork Join框架
      • Java并发-调试与诊断
    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 并发
轩辕李
2021-11-07
目录

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这样的工具时,遇到多线程竞争问题,思维还停留在加锁层面。而脑海中有了更多的工具箱时,一些情况下解决问题的思路也会随之打开!

祝你变得更强!

编辑 (opens new window)
#ThreadLocal
上次更新: 2023/06/14
Java并发-阻塞队列BlockingQueue
Java并发-无锁策略CAS与atomic包

← Java并发-阻塞队列BlockingQueue Java并发-无锁策略CAS与atomic包→

最近更新
01
Spring Boot版本新特性
09-15
02
Spring框架版本新特性
09-01
03
Spring Boot开发初体验
08-15
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式