Skip to content
清晨的一缕阳光
返回

ThreadLocal 原理与实战

ThreadLocal 原理与实战

ThreadLocal 实现线程隔离的数据存储,是用户上下文、数据库连接等场景的核心技术。

一、核心概念

1.1 什么是 ThreadLocal

ThreadLocal = 线程本地变量

每个线程都有自己独立的变量副本

线程间数据隔离,互不干扰

无需同步,提高性能

1.2 典型应用场景

1. 用户上下文(UserContext)
2. 数据库连接(Connection)
3. Session 管理
4. 事务管理
5. TraceId 追踪

二、基本使用

2.1 创建 ThreadLocal

// 创建 ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();

// 设置值
threadLocal.set("Hello");

// 获取值
String value = threadLocal.get();

// 移除值
threadLocal.remove();

2.2 线程隔离示例

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

// 线程 A
new Thread(() -> {
    threadLocal.set(1);
    System.out.println(threadLocal.get()); // 1
}).start();

// 线程 B
new Thread(() -> {
    threadLocal.set(2);
    System.out.println(threadLocal.get()); // 2
}).start();

// 主线程
System.out.println(threadLocal.get()); // null

2.3 初始值

ThreadLocal<SimpleDateFormat> threadLocal = 
    new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

// 使用
String date = threadLocal.get().format(new Date());

三、实现原理

3.1 核心结构

graph TB
    A[Thread] --> B[ThreadLocalMap]
    B --> C[Entry 1]
    B --> D[Entry 2]
    C --> E[ThreadLocal 引用]
    C --> F[Value]

3.2 源码分析

// Thread 类中
ThreadLocal.ThreadLocalMap threadLocals;

// ThreadLocalMap 结构
static class ThreadLocalMap {
    // Entry 数组
    private Entry[] table;
    
    // Entry 继承 WeakReference
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        
        Entry(ThreadLocal<?> k, Object v) {
            super(k); // 弱引用 ThreadLocal
            value = v;
        }
    }
}

// set() 方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// get() 方法
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

3.3 哈希冲突解决

// 线性探测法
// 如果 hash 位置被占用,向后查找空位

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    
    // 线性探测
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        
        if (e.get() == key) {
            e.value = value;
            return;
        }
    }
    
    // 找到空位,插入
    tab[i] = new Entry(key, value);
}

四、内存泄漏问题

4.1 为什么使用弱引用

ThreadLocalMap 的 Entry 继承 WeakReference

key 是 ThreadLocal 的弱引用

ThreadLocal 对象可被 GC 回收

4.2 内存泄漏场景

// ❌ 内存泄漏
ThreadLocal<User> userLocal = new ThreadLocal<>();

// 线程池场景
executor.submit(() -> {
    userLocal.set(currentUser); // 设置用户
    doWork();
    // 忘记 remove,线程复用导致内存泄漏
});

泄漏原因

  1. 线程池线程长期存活
  2. ThreadLocalMap 持有 value 强引用
  3. ThreadLocal 被回收,但 value 无法回收

4.3 解决方案

// ✅ 正确做法:finally 中 remove
ThreadLocal<User> userLocal = new ThreadLocal<>();

executor.submit(() -> {
    try {
        userLocal.set(currentUser);
        doWork();
    } finally {
        userLocal.remove(); // 必须清理
    }
});

4.4 InheritableThreadLocal 泄漏

// InheritableThreadLocal 父子线程共享
InheritableThreadLocal<String> local = new InheritableThreadLocal<>();

// 主线程设置
local.set("parent");

// 子线程继承
new Thread(() -> {
    System.out.println(local.get()); // "parent"
}).start();

// 问题:子线程修改不影响父线程
// 解决:使用 ChildInheritableThreadLocal 或手动传递

五、实战场景

5.1 用户上下文

public class UserContext {
    private static final ThreadLocal<User> userLocal = 
        ThreadLocal.withInitial(() -> null);
    
    public static void setUser(User user) {
        userLocal.set(user);
    }
    
    public static User getUser() {
        return userLocal.get();
    }
    
    public static void clear() {
        userLocal.remove();
    }
}

// 使用
public class UserService {
    public void process() {
        try {
            UserContext.setUser(currentUser);
            // 业务逻辑
        } finally {
            UserContext.clear();
        }
    }
}

5.2 数据库连接管理

public class DBConnection {
    private static final ThreadLocal<Connection> connectionLocal = 
        ThreadLocal.withInitial(() -> {
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });
    
    public static Connection getConnection() {
        return connectionLocal.get();
    }
    
    public static void closeConnection() {
        Connection conn = connectionLocal.get();
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        connectionLocal.remove();
    }
}

5.3 事务管理

public class TransactionManager {
    private static final ThreadLocal<Boolean> transactionLocal = 
        ThreadLocal.withInitial(() -> false);
    
    public void begin() {
        transactionLocal.set(true);
        DBConnection.getConnection().setAutoCommit(false);
    }
    
    public void commit() {
        try {
            DBConnection.getConnection().commit();
        } finally {
            transactionLocal.remove();
        }
    }
    
    public void rollback() {
        try {
            DBConnection.getConnection().rollback();
        } finally {
            transactionLocal.remove();
        }
    }
    
    public boolean isInTransaction() {
        return transactionLocal.get();
    }
}

5.4 TraceId 追踪

public class TraceContext {
    private static final ThreadLocal<String> traceLocal = 
        ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
    
    public static String getTraceId() {
        return traceLocal.get();
    }
    
    public static void setTraceId(String traceId) {
        traceLocal.set(traceId);
    }
    
    public static void clear() {
        traceLocal.remove();
    }
}

// 使用
@Slf4j
public class Service {
    public void process() {
        try {
            TraceContext.setTraceId(generateTraceId());
            log.info("Processing with traceId: {}", TraceContext.getTraceId());
        } finally {
            TraceContext.clear();
        }
    }
}

六、最佳实践

6.1 必须清理

// ❌ 错误:不清理
threadLocal.set(value);
doWork();
// 内存泄漏

// ✅ 正确:finally 清理
try {
    threadLocal.set(value);
    doWork();
} finally {
    threadLocal.remove();
}

6.2 线程池场景

// 线程池必须清理
executor.submit(() -> {
    try {
        context.set(ctx);
        return doWork();
    } finally {
        context.remove();
    }
});

6.3 初始值优化

// 使用 withInitial 代替匿名类
ThreadLocal<SimpleDateFormat> local = 
    ThreadLocal.withInitial(SimpleDateFormat::new);

// 优于
ThreadLocal<SimpleDateFormat> local = 
    new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat();
        }
    };

6.4 避免过度使用

// ❌ 不推荐:方法参数传递更清晰
public void process(User user) {
    doSomething(user);
}

// ✅ 推荐:跨多层调用
public void process() {
    UserContext.setUser(user);
    service.doSomething(); // 深层调用可获取 user
}

七、总结

ThreadLocal 核心要点:

特性说明
线程隔离每个线程独立副本
实现原理ThreadLocalMap + Entry
弱引用key 是弱引用,防止泄漏
内存泄漏线程池场景必须 remove
应用场景用户上下文、数据库连接、TraceId

ThreadLocal 是线程隔离的利器,但必须注意内存泄漏问题,finally 中务必 remove。


分享这篇文章到:

上一篇文章
ArrayList vs LinkedList 详解
下一篇文章
并发工具类实战