ThreadLocal是Java中一个特殊的工具类,它提供了线程局部变量。这些变量不同于普通的变量,因为每个访问该变量的线程都有自己独立初始化的变量副本,从而避免了多线程环境下的共享问题。
ThreadLocal的主要作用是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。这种机制有效地解决了多线程并发访问时的线程安全问题。
ThreadLocal的典型使用场景包括:
ThreadLocal的核心数据结构实际上是一个"线程 -> 线程局部变量"的映射,但这种映射并不是通过ThreadLocal直接维护的,而是通过每个Thread对象内部的一个特殊Map来实现。
在JDK中,这个映射由Thread类中的threadLocals字段维护,它是ThreadLocalMap类型的。ThreadLocalMap是一个定制化的哈希表,专门用于存储线程局部变量。
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();
public static void main(String[] args) {
// 设置初始值
threadLocalCounter.set(0);
// 启动多个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
// 获取当前线程的值
Integer counter = threadLocalCounter.get();
if (counter == null) {
counter = 0;
threadLocalCounter.set(counter);
}
// 增加值
counter++;
threadLocalCounter.set(counter);
System.out.println(Thread.currentThread().getName()
+ ": " + threadLocalCounter.get());
}).start();
}
}
}在JDK1.7中,ThreadLocalMap的实现较为简单,使用线性探测法解决哈希冲突。Entry继承自WeakReference,键(ThreadLocal对象)是弱引用,但值仍然是强引用。
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}JDK1.8对ThreadLocalMap进行了一些优化:
虽然基本结构没有变化,但1.8版本的实现更加高效,特别是在高并发场景下表现更好。
ThreadLocalMap中的Entry继承自WeakReference,键(ThreadLocal对象)是弱引用,而值是强引用。这意味着:
内存泄漏的根本原因是:当ThreadLocal对象被回收后,Map中对应的Entry键变为null,但值仍然存在且无法被访问到(因为需要通过ThreadLocal对象作为键来访问)。如果线程长时间运行(如线程池中的线程),这些无用的值会一直占用内存。
try {
// 使用threadLocal
threadLocal.set(someValue);
// ...其他操作
} finally {
// 确保清除
threadLocal.remove();
}private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));InheritableThreadLocal是ThreadLocal的子类,它允许子线程继承父线程的线程局部变量。
public class InheritableThreadLocalExample {
private static final InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("main thread value");
new Thread(() -> {
System.out.println("Child thread get: " + inheritableThreadLocal.get());
}).start();
}
}ThreadLocalMap是一个定制化的哈希表,它使用开放地址法解决哈希冲突。与HashMap不同,它不采用链表法。
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();
}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();
}ThreadLocal是Java多线程编程中一个非常有用的工具,它通过为每个线程提供独立的变量副本来解决线程安全问题。正确理解其实现原理和使用场景,可以帮助我们编写更健壮的多线程程序。
关键要点:
ThreadLocal虽然强大,但也要谨慎使用。过度使用ThreadLocal可能导致内存占用过高,不合理的使用可能导致内存泄漏。理解其原理和潜在问题,才能更好地发挥它的作用。