关键策略是ThreadLocal+连接池按需获取:每个线程独享连接,避免争抢;static Connection因全局共享导致事务混乱和串行化,不可用;ThreadLocal通过线程内映射表隔离连接,须严格执行获取、使用、清理三步以防泄漏。

多线程环境下,让每个线程拥有自己独享的数据库连接,关键不是“把连接塞进线程里”,而是避免多个线程争抢同一个连接对象。核心策略是:连接本身不共享,连接的“使用上下文”要隔离——最常用、最稳妥的方式就是 ThreadLocal + 连接池按需获取,而不是复用静态或全局连接变量。
为什么不能直接用 static Connection?
static 变量被所有线程共用,一旦线程 A 正在执行 SQL,线程 B 调用 close() 或 setAutoCommit(false),A 就会立刻报错或事务混乱。这不是并发控制问题,而是根本违反了连接“一次一用、用完即还”的契约。
即使加 synchronized,也会把数据库操作串行化,吞吐量断崖式下降,失去多线程意义。
ThreadLocal 是怎么做到连接独享的?
它不存储连接本身,而是在每个线程内部维护一个映射表(ThreadLocalMap),以当前 ThreadLocal 实例为 key,存入该线程专属的 Connection 对象。不同线程调用同一个 ThreadLocal 的 get(),拿到的是各自 map 里的值,互不触碰。
- 主线程 set(conn1),子线程 set(conn2),彼此看不到对方的 conn
- 同一线程内多次 get() 返回同一个 conn,适合贯穿 DAO 层的事务控制
- 连接由连接池(如 HikariCP)提供,ThreadLocal 只负责“绑定”和“解绑”
实际写法要注意三个动作
一个健壮的 ThreadLocal 连接管理必须包含获取、使用、清理三步,缺一不可:
- 获取:在业务开始前(如 Filter 或 AOP 切面中),检查 ThreadLocal 是否为空;为空则从 DataSource 获取新连接,并 set 进去
- 使用:DAO 层通过同一 ThreadLocal 实例 get() 拿到连接,执行 query/update/commit/rollback
- 清理:请求结束(如 finally 块或拦截器 afterCompletion)必须调用 remove(),否则在线程池场景下,线程被复用时旧连接残留,可能造成连接泄漏或事务污染
比 ThreadLocal 更彻底的隔离:连接池直连模式
某些轻量场景(如批处理、脚本工具类),可跳过 ThreadLocal,改为每次需要时从连接池获取连接,用完立即 close()。虽然看似重复创建,但现代连接池(如 HikariCP)的 getConnection() 几乎是毫秒级的,且连接复用由池内部管理,无需人工绑定。
这种方式天然无状态、无残留,适合短生命周期任务,缺点是无法跨方法传递同一个连接(比如想手动控制事务边界时就不够灵活)。

















