一句话说透Android里面的ThreadLocal的使用和原理

211 阅读2分钟

一句话总结:

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()  
    

底层原理(源码解析)

  1. 每个线程(Thread)内部持有 ThreadLocalMap

    class Thread {  
        ThreadLocal.ThreadLocalMap threadLocals;  
    }  
    
  2. ThreadLocalMap 的 Key 是弱引用

    • 防止 ThreadLocal 对象内存泄漏(但 Value 仍是强引用,需手动 remove())。
  3. 存取逻辑

    • threadLocal.set(value) → 当前线程的 ThreadLocalMap 存入 key=threadLocal, value=value
    • threadLocal.get() → 从当前线程的 ThreadLocalMap 用 threadLocal 作为 Key 取出 Value。

注意事项(避坑指南)

  1. 内存泄漏风险

    • 原因:线程池中的线程可能长期存活,若未调用 remove(),Value 会一直驻留内存。

    • 解决:用完后主动清理:

      try {  
          threadLocal.set(data)  
          // ... 业务逻辑  
      } finally {  
          threadLocal.remove()  
      }  
      
  2. 避免全局共享的 ThreadLocal

    • 错误做法:在多个模块共用同一个 ThreadLocal,导致 Key 冲突。
    • 正确做法:每个模块定义自己的 ThreadLocal 实例。
  3. 初始值

    • 可通过重写 initialValue() 设置默认值,避免返回 null

实际应用案例(Android 源码)

  • Looper 中的 sThreadLocal

    // 每个线程的 Looper 存在自己的 ThreadLocal 中  
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  
    
  • 保证每个线程有独立的 Looper,Handler 能正确关联到当前线程的消息队列。


总结口诀

“ThreadLocal 是储物柜,线程数据各存各,
避免竞争性能高,用完记得清角落。
弱引用 Key 防泄漏,初始值可覆盖写,
隐式传参好帮手,合理使用别翻车!