一句话总结:
ThreadLocal 是“线程的私人储物柜”,每个线程往里面存东西互不干扰,避免多线程打架,但用完后记得清空,否则可能内存泄漏!
核心原理(储物柜比喻)
-
每个线程有一个“储物柜” (即
ThreadLocalMap),存放自己的私有数据。 -
存取规则:通过
ThreadLocal对象作为钥匙,每个线程用同一把钥匙开自己的柜子,拿到的数据不同。 -
代码示例:
// 创建一个 ThreadLocal 储物柜 val threadLocal = ThreadLocal<String>() // 线程A存数据 threadLocal.set("A的数据") // 线程B存数据 threadLocal.set("B的数据") // 线程A取数据 → "A的数据" // 线程B取数据 → "B的数据"
使用场景
1. 避免参数传递(隐式传参)
-
场景:在复杂调用链中,跨多个方法传递参数繁琐。
-
示例:用户身份信息(如用户ID)在请求处理线程中全局可用。
object UserContext { private val currentUserId = ThreadLocal<Long>() fun setUserId(id: Long) { currentUserId.set(id) } fun getUserId() = currentUserId.get() } // 在任意位置获取用户ID val userId = UserContext.getUserId()
2. 线程封闭资源(如数据库连接)
-
场景:每个线程需要独立的资源实例(避免线程不安全)。
-
示例:
val dbConnectionLocal = object : ThreadLocal<Connection>() { override fun initialValue(): Connection { return DriverManager.getConnection(DB_URL) } } // 每个线程获取自己的 Connection val connection = dbConnectionLocal.get()
底层原理(源码解析)
-
每个线程(Thread)内部持有
ThreadLocalMap:class Thread { ThreadLocal.ThreadLocalMap threadLocals; } -
ThreadLocalMap 的 Key 是弱引用:
- 防止
ThreadLocal对象内存泄漏(但 Value 仍是强引用,需手动remove())。
- 防止
-
存取逻辑:
threadLocal.set(value)→ 当前线程的ThreadLocalMap存入key=threadLocal, value=value。threadLocal.get()→ 从当前线程的ThreadLocalMap用threadLocal作为 Key 取出 Value。
注意事项(避坑指南)
-
内存泄漏风险:
-
原因:线程池中的线程可能长期存活,若未调用
remove(),Value 会一直驻留内存。 -
解决:用完后主动清理:
try { threadLocal.set(data) // ... 业务逻辑 } finally { threadLocal.remove() }
-
-
避免全局共享的 ThreadLocal:
- 错误做法:在多个模块共用同一个
ThreadLocal,导致 Key 冲突。 - 正确做法:每个模块定义自己的
ThreadLocal实例。
- 错误做法:在多个模块共用同一个
-
初始值:
- 可通过重写
initialValue()设置默认值,避免返回null。
- 可通过重写
实际应用案例(Android 源码)
-
Looper 中的 sThreadLocal:
// 每个线程的 Looper 存在自己的 ThreadLocal 中 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); -
保证每个线程有独立的 Looper,Handler 能正确关联到当前线程的消息队列。
总结口诀
“ThreadLocal 是储物柜,线程数据各存各,
避免竞争性能高,用完记得清角落。
弱引用 Key 防泄漏,初始值可覆盖写,
隐式传参好帮手,合理使用别翻车!