ThreadLocal用法及实现原理解析
ThreadLocal 基本用法
1. 创建和基本使用
public class ThreadLocalDemo {// 创建ThreadLocal变量private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 在主线程中设置值threadLocal.set("主线程的值");// 创建新线程Thread thread = new Thread(() -> {// 在新线程中设置值threadLocal.set("子线程的值");System.out.println("子线程: " + threadLocal.get());});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}// 主线程获取自己的值System.out.println("主线程: " + threadLocal.get());}
}输出结果:
子线程: 子线程的值 主线程: 主线程的值
2. 使用初始值
public class ThreadLocalWithInitialValue {// 使用withInitial提供初始值private static ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);public static void main(String[] args) {// 每个线程第一次访问时都会使用初始值System.out.println("初始值: " + counter.get());counter.set(counter.get() + 1);System.out.println("增加值后: " + counter.get());}
}3. 实际应用场景 - 用户上下文管理
public class UserContext {private static ThreadLocal<User> userContext = new ThreadLocal<>();public static void setCurrentUser(User user) {userContext.set(user);}public static User getCurrentUser() {return userContext.get();}public static void clear() {userContext.remove();}// 用户类static class User {private String name;private String id;public User(String name, String id) {this.name = name;this.id = id;}// getters and setters...}
}// 使用示例
public class UserService {public void processUserRequest() {// 在过滤器或拦截器中设置用户信息UserContext.setCurrentUser(new User("张三", "123"));try {// 业务处理,任何地方都可以获取用户信息doBusinessLogic();} finally {// 清理,防止内存泄漏UserContext.clear();}}private void doBusinessLogic() {User user = UserContext.getCurrentUser();System.out.println("处理用户: " + user.getName());}
}ThreadLocal 实现原理
核心数据结构
// Thread类中有个ThreadLocalMap类型的变量
public class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;
}// ThreadLocalMap是ThreadLocal的内部静态类
static class ThreadLocalMap {// Entry继承自WeakReference,key是弱引用static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);  // 弱引用value = v;}}private Entry[] table;private int size = 0;
}源码分析
1. 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);}
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}2. get方法
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}private T setInitialValue() {T value = initialValue();  // 可重写的方法Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}return value;
}3. ThreadLocalMap的set方法
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)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}内存泄漏问题
问题根源
// Entry的key是弱引用,value是强引用
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;  // 强引用!Entry(ThreadLocal<?> k, Object v) {super(k);  // 弱引用value = v;}
}内存泄漏场景
public class MemoryLeakDemo {public static void main(String[] args) {ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();// 设置大对象threadLocal.set(new byte[1024 * 1024 * 10]); // 10MB// 线程结束前没有remove,可能导致内存泄漏// threadLocal.remove(); // 应该调用这个}
}正确用法
public class CorrectUsage {private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public void process() {try {SimpleDateFormat format = dateFormatThreadLocal.get();// 使用format...} finally {// 重要:使用完毕后清理dateFormatThreadLocal.remove();}}
}使用建议和最佳实践
1. 使用try-finally确保清理
public class SafeThreadLocalUsage {private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();public void doInTransaction() {try {Connection conn = getConnection();connectionHolder.set(conn);// 业务逻辑...} finally {Connection conn = connectionHolder.get();if (conn != null) {connectionHolder.remove();// 其他清理工作...}}}
}2. 使用InheritableThreadLocal传递线程上下文
public class InheritableThreadLocalDemo {private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {inheritableThreadLocal.set("父线程的值");Thread childThread = new Thread(() -> {// 子线程可以继承父线程的值System.out.println("子线程获取: " + inheritableThreadLocal.get());});childThread.start();}
}总结
ThreadLocal的核心特点:
每个线程有自己独立的变量副本
通过Thread内部的ThreadLocalMap实现
key是ThreadLocal对象的弱引用,value是强引用
需要手动管理内存,避免内存泄漏
适用场景:
线程上下文传递(用户信息、事务管理等)
线程安全的对象复用(如SimpleDateFormat)
避免方法参数传递
注意事项:
使用后务必调用remove()清理
避免存储大对象
考虑使用try-finally确保清理
