Android 线程本地变量<二> ThreadLocal Values源码解析

本文详细探讨了Android中ThreadLocal的静态内部类Values的实现,包括其数据存储结构、成员变量、构造方法以及关键操作如put、cleanUp、rehash和remove等。通过分析,揭示了Values如何管理线程变量并进行内存优化。

Android 线程本地变量<二> ThreadLocal Values源码解析

@(Android系统源码解析)[Android, ThreadLocal, Values]

声明:转载请注明出处,知识有限,如有错误,请多多交流指正!

注:基于Android 6.0(API 23)源码

ThreadLocal内部类Values是被设计用来保存线程的变量的一个类,它相当于一个容器,存储保存进来的变量

静态内部类Values类解析

1. Values的结构
Values

主要原理是Values将数据存储在数组table(Object[] table)中,那么键值对如何存放的呢?就是table被设计为下标为0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。取数据的时候可以直接通过下标存取线程变量。

2. Values的内部实现
成员变量
- private static final Object TOMBSTONE = new Object(); // 表示被删除的实体标记,移除变量时它是把对应的key的位置赋值为TOMBSTONE
- private static final int INITIAL_SIZE = 16; //默认的初始化容量
- private Object[] table; //保存变量的地方,长度必须是2的n次方的值
- private int mask; //计算下标的掩码,它的值是table的长度-1
- private int size; //存放进来的实体的数量
- private inttombstones; //表示被删除的实体的数量
- private int maximumLoad; //阈值,用来判断是否需要进行rehash
- private int clean; //表示下一个要进行清理的位置点


Values的构造

  Values() {
           // 默认创建32个长度的容器table
            initializeTable(INITIAL_SIZE);
            this.size = 0;
            this.tombstones = 0;
  }

 private void initializeTable(int capacity) {
            // 通过capacity创建table容器 
            this.table = new Object[capacity * 2];
            // 下标的掩码
            this.mask = table.length - 1;
            this.clean = 0;
            // 2/3 最大负载因子
            this.maximumLoad = capacity * 2 / 3; 
 }

Values的方法
- void put(ThreadLocal

void put(ThreadLocal<?> key, Object value) {
            //清理了废弃的元素
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            // 通过key获取索引index
            for (int index = key.hash & mask;; index = next(index)) {
                // 通过索引获取TheadLocal
                Object k = table[index];

                // 如果TheadLocal是存在,直接返回
                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    // 找到firstTombstone这个索引值,然后赋值对应的key和value
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }
  • private void cleanUp()
private void cleanUp() {
            if (rehash()) {
                // If we rehashed, we needn't clean up (clean up happens as
                // a side effect).
                return;
            }

            if (size == 0) {
                // No live entries == nothing to clean.
                return;
            }

            // Clean log(table.length) entries picking up where we left off
            // last time.
            int index = clean;
            Object[] table = this.table;
            for (int counter = table.length; counter > 0; counter >>= 1,index = next(index)) {
                Object k = table[index];

                // 如果已经删除,则删除下一个
                if (k == TOMBSTONE || k == null) {
                    continue; // on to next entry
                }

                // The table can only contain null, tombstones and references.
                @SuppressWarnings("unchecked")
                Reference<ThreadLocal<?>> reference= (Reference<ThreadLocal<?>>) k;
                // 如果ThreadLocal已经不存在,则释放value数据
                if (reference.get() == null) {
                    // This thread local was reclaimed by the garbage collector.
                    table[index] = TOMBSTONE; //已经被释放了
                    table[index + 1] = null;
                    tombstones++; //被删除的数量
                    size--;
                }
            }

            // Point cursor to next index.
            clean = index;
        }
  • void add(ThreadLocal
   void add(ThreadLocal<?> key, Object value) {
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];
                if (k == null) {
                    // 把ThreadLocal对象(key)和对应的value放在连续的位置中
                    // table被设计为下标为0,2,4...2n的位置存放key,而1,3,5...(2n +1 )的位置存放value
                    table[index] = key.reference;
                    table[index + 1] = value;
                    return;
                }
            }
 }
  • private boolean rehash()
    数组肯定要扩容,就是当前正在使用的内存大于分配内存的1/2的时候进行数扩容

  • void remove(ThreadLocal key)
    用来释放内存,清除数据

<think>我们正在讨论Java中ThreadLocal在Web应用序中的使用问题。根据引用内容,主要问题是ThreadLocal可能导致类加载器泄漏,从而使得Web应用序无法正确卸载。具体来说,Web服务器(如Tomcat)使用线程池,这些线程的生命周期比单个Web应用更长。如果ThreadLocal变量引用了某个类,而该类是由Web应用类加载器加载的,那么即使应用被卸载,由于线程池中的线程仍然持有对该类的引用,该类加载器也无法被垃圾回收,导致内存泄漏。引用[1]和[2]明确指出,ThreadLocal在Web应用中可能导致类加载器泄漏,因为线程池中的线程存活时间很长,而ThreadLocal如果没有被正确清理,就会阻止Web应用类加载器被回收。 引用[4]和[5]则提供了一些解决方案,比如使用一个工具类集中管理ThreadLocal,并在适当的时候(如请求结束时)调用remove()方法清理。因此,回答应围绕以下点展开:1.问题:ThreadLocal在Web应用中可能导致类加载器泄漏(内存泄漏)。2.原因:线程池中的线程存活时间长,ThreadLocal中存储的对象如果与Web应用类加载器有关,则会导致该类加载器无法被回收。3.解决方案:在请求结束时(如使用ServletRequestListener或Filter)显式调用ThreadLocal的remove()方法。 同时,我们还可以给出一个使用示例和最佳实践。 注意:回答中要引用给定的引用内容,并在末尾添加引用标识。另外,根据用户上一次的问题(关于URL编码)和本次问题,用户意图是了解ThreadLocal在Web应用中的潜在问题和风险。因此,回答结构如下:1.问题描述:ThreadLocal在Web应用中可能导致类加载器泄漏。2.原因分析:线程池机制和类加载器引用。3.解决方案:显式调用remove()方法,使用工具类集中管理。 4.示例代码:展示如何安全使用ThreadLocal。5.最佳实践:建议在finally块中调用remove()。6.相关问题:生成相关问题。注意:代码示例参考引用[5]的Session管理,但要注意安全清理。</think>### Java ThreadLocal 在 Web 应用中的线程管理问题及解决方案 #### 1. **类加载器泄漏(ClassLoader Leaks)**[^1][^2] - **根本原因**:Web 服务器(如 Tomcat)使用线程池,线程生命周期远超单个 Web 应用。当 `ThreadLocal` 存储由 Web 应用类加载器加载的对象时: ```mermaid graph LR A[线程线程] --> B[ThreadLocal变量] B --> C[应用类对象] C --> D[Web应用类加载器] ``` - **后果**: - 应用卸载(undeploy)后,线程线程仍持有 `ThreadLocal` 引用 - 类加载器无法被 GC 回收 → 永久内存泄漏 - 多次重部署后导致 `OutOfMemoryError` #### 2. **线程污染(Thread Pollution)**[^4] - **场景**:线程池复用线程处理不同请求时 - 请求 A 在 `ThreadLocal` 存储数据 X - 请求 B 复用同一线程,意外读取到残留数据 X - **风险**: - 用户会话混淆(如引用[5]的 Session 管理) - 敏感数据泄露 - 业务逻辑错误 #### 3. **资源泄漏(Resource Leaks)**[^5] - **典型案例**:数据库连接未释放 ```java private static final ThreadLocal<Connection> connHolder = new ThreadLocal<>(); public Connection getConnection() { Connection conn = connHolder.get(); if (conn == null) { conn = dataSource.getConnection(); // 创建新连接 connHolder.set(conn); } return conn; } ``` - **问题**:未调用 `connHolder.remove()` 时: - 连接无法归还连接池 - 连接耗尽导致系统崩溃 --- ### 解决方案与实践 #### 1. **强制清理 ThreadLocal** [^4] 在请求处理结束时清理资源: ```java // 在 Filter 或 ServletRequestListener 中实现 public void requestDestroyed(ServletRequestEvent sre) { try { // 业务处理... } finally { threadSession.remove(); // 关键清理动作 } } ``` #### 2. **集中管理工具类** [^4] 创建 `ThreadLocalUtil` 统一管理: ```java public class ThreadLocalUtil { private static Map<String, ThreadLocal<?>> registry = new ConcurrentHashMap<>(); public static void register(String key, ThreadLocal<?> tl) { registry.put(key, tl); } public static void destroy() { // 在请求结束时调用 registry.values().forEach(ThreadLocal::remove); } } ``` #### 3. **最佳实践** 1. **作用域最小化** ```java // 错误:静态共享 private static ThreadLocal<User> userHolder = new ThreadLocal<>(); // 正确:方法内局部使用 public void processRequest() { ThreadLocal<User> requestScoped = new ThreadLocal<>(); try { // ...业务逻辑 } finally { requestScoped.remove(); // 必须清理 } } ``` 2. **防御性编** - 使用 `try-finally` 确保 `remove()` 执行: ```java public void doGet(HttpServletRequest req, HttpServletResponse resp) { try { threadLocal.set(...); // 处理请求 } finally { threadLocal.remove(); // 绝对执行 } } ``` 3. **替代方案** - 对于请求作用域数据,优先使用: - Servlet API 的 `request.setAttribute()` - Spring 的 `RequestScope` Bean - `ScopedProxy` 模式 --- ### 验证方法 1. **内存泄漏检测** ```bash jcmd <PID> GC.class_histogram | grep -i "ThreadLocal" ``` 2. **部署测试** - 反复部署/卸载应用,监控 `PermGen/Metaspace` 增长 3. **线程转储分析** ```bash jstack <PID> | grep -A10 "ThreadLocal" ``` > **关键原则**:Web 应用中 `ThreadLocal` 必须视为 **短期存储**,在请求结束时通过 `remove()` 显式清理。优先使用框架提供的请求作用域机制替代原生 `ThreadLocal`[^1][^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值