ThreadLocal 是 Java 提供的以一种方便的方式在一个线程内不同方法之间传递和获取对象的类。
使用
通过 set 方法可以为当前线程中的变量赋值
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello");
通过 get 方法获取变量的值
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello");
String value = threadLocal.get();
通过 withInitial 静态方法可以为变量设置初始化值
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "world");
通过继承 ThreadLocal 类并重写 initialValue 方法,可以在定义变量时为其赋初始值。
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "world";
}
};
示例代码如下:
public class ThreadLocalDemo {
private static ThreadLocal<AtomicInteger> counter = ThreadLocal.withInitial(AtomicInteger::new);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
for (int j = 0; j < 3; j++) {
increment();
System.out.printf("%s incremented value: %d, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, counter.get().get(), counter.hashCode(),counter.get().hashCode());
}
}, "thread-" + i).start();
}
}
private static void increment() {
counter.get().incrementAndGet();
}
}
这个例子中,每个线程都有自己独立的 AtomicInteger 实例,并且可以通过 incrementAndGet() 方法对其进行原子递增操作。每个线程递增的值都是独立的,不会相互干扰。代码输入如下:
thread-0 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-4 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-4 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-3 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-1 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-1 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-2 incremented value: 1, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391
thread-1 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1429559356
thread-3 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-4 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1441208256
thread-0 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-3 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 769479435
thread-2 incremented value: 2, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391
thread-0 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 505044840
thread-2 incremented value: 3, ThreadLocal instance hashcode is 282960643, ThreadLocal instance mapping value hashcode is 1547619391
从输出中可以看出,不同线程访问的是用一个counter对象(counter.hashCode()结果相同),但是每个线程获得的 counter 对象实例不同(counter.get().hashCode()结果不同)。
原理
ThreadLocal 能在每个线程间进行隔离,其主要是靠在每个 Thread 对象中维护一个 ThreadLocalMap 来实现的。因为是线程中的对象,所以对其他线程不可见,从而达到隔离的目的。使用 Map 结构主要是因为一个线程中可能有多个 ThreadLocal 对象,这就需要一个集合来进行存储区分,而用 Map 可以更快地查找到相关的对象。
ThreadLocalMap 是 ThreadLocal 对象的一个静态内部类,内部维护一个 Entry 数组,实现类似 Map 的 get 和 put 等操作,为简单起见,可以将其看做是一个 Map,其中 key 是 ThreadLocal 实例,value 是 ThreadLocal 实例对象存储的值。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
set 和 get
public void set(T value) {
Thread t = Thread.currentThread();
// 当前线程的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// this 指当前的 ThreadLocal 对象
map.set(this, value);
} else {
// key 不存在,则创建 map 并设置值
createMap(t, value);
}
}
public T get() {
Thread t = Thread.currentThread();
// 当前线程的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// this 指当前的 ThreadLocal 对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
其中 getMap 方法如下
ThreadLocalMap getMap(Thread t) {
// threadLocals 是 Thread 中的一个变量,因此是线程隔离的,不会受其他线程影响
// 其在 Thread 类中的定义如下:ThreadLocal.ThreadLocalMap threadLocals = null;
return t.threadLocals;
}
内存泄漏
根据上面Entry方法的源码,我们知道 ThreadLocalMap 使用 ThreadLocal 的弱引用作为 Key 的,而 value 却是一个强引用。
ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有外部强引用引用他,那么系统 gc 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。
感谢大家读到这里,后续还会有其他相关文章,欢迎继续阅读。
本文详细介绍了Java中的ThreadLocal类,包括如何在不同线程间传递和获取对象,ThreadLocalMap的工作原理,以及潜在的内存泄漏问题。作者通过示例展示了ThreadLocal在多线程环境下的应用和注意事项。
1161

被折叠的 条评论
为什么被折叠?



