ThreadLocal
是 Java 提供的一种机制,用于实现线程本地存储。每个线程都可以通过 ThreadLocal
保存和获取自己的独立变量,互不干扰。本文将通过源码解析和应用实战,全面解析 ThreadLocal
的工作原理和使用场景。
ThreadLocal 简介
在多线程编程中,多个线程访问共享变量时可能会导致线程安全问题。为了解决这一问题,ThreadLocal
提供了一种简单的方式:
- 每个线程拥有独立的变量副本。
- 不需要显式同步。
以下是一个简单的用法示例:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Thread A");
System.out.println(threadLocal.get()); // 每个线程获取自己的值
适用场景
- 线程隔离的变量存储。
- 数据库连接、会话管理等需要线程独占资源的场景。
ThreadLocal 源码解析
内部结构
ThreadLocal
:当前线程的局部变量。ThreadLocalMap
:存储所有ThreadLocal
对象和其对应值的映射关系。Entry
:ThreadLocal
和其值的键值对。
ThreadLocal
的核心数据结构依赖于每个线程维护的 ThreadLocalMap。
ThreadLocalMap 的设计
ThreadLocalMap
是一个定制的哈希表,用于存储ThreadLocal
和其对应的值。Entry
使用弱引用存储ThreadLocal
对象,以便垃圾回收器可以回收不再使用的键。- 使用 线性探测 来解决哈希冲突,确保高效的存取操作。
关键字段:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private int size;
private int threshold; // 调整大小的阈值
}
线性探测的关键方法:
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);
size++;
if (size >= threshold) {
resize();
}
}
核心方法解析
set()
set(T value)
方法用于设置当前线程的变量值。主要逻辑:
- 获取当前线程的
ThreadLocalMap
。 - 如果
ThreadLocalMap
不存在,则初始化一个新的。 - 在
ThreadLocalMap
中存储ThreadLocal
和对应的值。
部分源码:
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()
get()
方法用于获取当前线程的变量值。
- 获取当前线程的
ThreadLocalMap
。 - 如果存在,则返回对应的值;否则,初始化默认值。
源码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T) e.value;
}
}
return setInitialValue();
}
remove()
remove()
方法用于清除当前线程的变量值,避免内存泄漏。
public void remove() {
ThreadLocalMap map = getMap(Thread.currentThread());
if (map != null) {
map.remove(this);
}
}
ThreadLocal 内存泄漏问题
原因
ThreadLocalMap
使用弱引用存储 ThreadLocal
对象,但值是强引用。如果 ThreadLocal
对象被垃圾回收,其对应的值将无法被访问,但仍可能存活,导致内存泄漏。
解决方案
- 手动调用
remove()
:在变量使用完后,主动清理。 - 谨慎使用
ThreadLocal
:避免保存生命周期较长的对象。
应用实战
以下通过 Android Looper
的源码展示 ThreadLocal
的实际应用。
示例:Looper 的线程管理
在 Android 中,每个线程可以拥有一个独立的 Looper
,通过 ThreadLocal
实现线程与 Looper
的绑定关系。
Looper 源码解析
ThreadLocal
的声明:
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
- prepare() 方法:
为当前线程创建一个 Looper
实例,并通过 ThreadLocal
绑定。
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
- myLooper() 方法:
通过 ThreadLocal
获取当前线程的 Looper
。
public static Looper myLooper() {
return sThreadLocal.get();
}
代码示例
以下展示如何使用 Looper
管理线程:
public class LooperThread extends Thread {
@Override
public void run() {
// 准备 Looper
Looper.prepare();
System.out.println("Looper prepared for thread: " + Thread.currentThread().getName());
// 获取当前线程的 Looper
Looper looper = Looper.myLooper();
System.out.println("Looper: " + looper);
// 启动消息循环
Looper.loop();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
LooperThread thread = new LooperThread();
thread.start();
}
}
效果图
总结
ThreadLocal
提供了一种优雅的线程本地变量存储方式,在多线程编程中非常实用。通过本文的源码解析和实际应用示例,相信你对 ThreadLocal
的工作机制和应用场景有了更深入的理解。谨记在使用时注意内存泄漏问题,以确保系统的稳定性。