1. 什么是 ThreadLocal
在 Java 多线程编程中,我们经常会遇到共享变量的并发访问问题。为了解决这个问题,Java 提供了 ThreadLocal 类,它允许我们在每个线程中存储和访问线程局部变量,而不会影响其他线程的数据。
2. 使用 ThreadLocal
使用 ThreadLocal 很简单,我们只需要创建一个 ThreadLocal 对象,然后使用 set() 方法设置值,使用 get() 方法获取值即可。
2.1 两个线程使用一个ThreadLoal变量
点击查看代码
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
Thread t1 = new Thread(() -> {
local.set("t1");
System.out.println("tid=" + Thread.currentThread() + ", local=" + local + ",local val =" + local.get());
});
t1.start();
Thread t2 = new Thread(() -> {
local.set("t2");
System.out.println("tid=" + Thread.currentThread() + ", local=" + local + ",local val =" + local.get());
});
t2.start();
}
运行结果:
从以上执行结果可以看出,创建的ThreadLocal变量 local在线程t1和t2中是同一个变量,但是通过set()方法设置数据后,保存的却是各自的副本,再使用get()方法访问的是各自的数据,实现了不同线程间数据的隔离。
2.2 单个线程有两个ThreadLoal变量
点击查看代码
public static void main(String[] args) {
ThreadLocal<String> local1 = new ThreadLocal<>();
ThreadLocal<String> local2 = new ThreadLocal<>();
Thread t1 = new Thread(() -> {
local1.set("v1");
local2.set("v2");
System.out.println("tid=" + Thread.currentThread() + ", local1=" + local1 + ", val =" + local1.get());
System.out.println("tid=" + Thread.currentThread() + ", local1=" + local2 + ", val =" + local2.get());
});
t1.start();
}
运行结果:
线程可以有多个threadLocal变量,他们之间也是相互独立的。
3. 实现原理
ThreadLocal 的实现原理主要涉及三个关键点:ThreadLocal 类、Thread 类和 ThreadLocalMap 类。
- ThreadLocal 类是 ThreadLocal 变量的容器,每个线程中可以定义多个 ThreadLocal 对象。
- Thread 类是 Java 中表示线程的类,每个线程都有一个 ThreadLocalMap 对象。
点击查看代码
public
class Thread implements Runnable {
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* omit some. */
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
- ThreadLocalMap 类是 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;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
当我们使用 ThreadLocal 设置值时,值被存储在当前线程的 ThreadLocalMap 中。在当前线程中,我们可以通过 ThreadLocal 对象来获取和更新这些值,而不会干扰其他线程的数据。
3.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);
}
}
ThreadLocal的set()方法在执行时,首先获取当前线程中对应的 ThreadLocalMap 对象。
如果当前线程的 ThreadLocalMap 为 null,则需要先进行初始化。调用 createMap() 方法创建一个新的 ThreadLocalMap 对象,并将其设置到当前线程中。
ThreadLocalMap 是一个自定义的哈希表结构,用于存储 ThreadLocal 对象和对应的值。在 ThreadLocalMap 中,ThreadLocal 实例是弱引用,而值则是强引用。
接下来,set() 方法会将根据当前 ThreadLocal 实例作为键,将传入的值作为值,创建出一个Entry,存储到 ThreadLocalMap 的Entry[] tab数组中。
ThreadLocalMap 使用线性探测法解决哈希冲突,并使用开放地址法进行存储。当插入新的键值对时,会遍历数组,找到合适的位置进行插入。如果发生哈希冲突,则会继续向后查找空槽进行插入。
存储完成后,当前线程的 ThreadLocalMap 中就包含了该 ThreadLocal 实例及其对应的值。
3.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 Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
get() 方法会获取当前线程中的 ThreadLocalMap 对象
如果当前线程的 ThreadLocalMap 为 null,表示当前线程没有使用 ThreadLocal,则直接返回 null。
如果当前线程的 ThreadLocalMap 不为 null,则通过当前 ThreadLocal 实例作为键,从 ThreadLocalMap 中获取对应的值。 从key.threadLocalHashCode & (table.length - 1)可知ThreadLocal 实例在每个线程中Entry[]table数组的下标是固定的。
在 ThreadLocalMap 中,会根据 ThreadLocal 实例的哈希值进行索引,查找对应的存储位置。
如果找到对应的位置,即表示当前线程已经使用过该 ThreadLocal 实例,可以直接返回存储的值。
如果未找到对应的位置,或者位置对应的 ThreadLocal 实例与当前 ThreadLocal 实例不匹配,即表示当前线程没有使用过该 ThreadLocal 实例,返回 null。
3.3 三者之间的关系图
4. ThreadLocal 的应用场景
ThreadLocal 在许多场景下非常有用,特别是在以下情况下:
- 多线程环境下的数据隔离:当多个线程需要访问同一个对象的数据时,使用 ThreadLocal 可以避免线程间的数据竞争和并发访问问题。
- 线程上下文传递:在跨线程的业务逻辑中,可以使用
5. 注意事项
-
内存泄漏问题:由于 ThreadLocal 使用了线程的唯一标识作为索引,在使用完毕后,如果没有手动清理或及时移除 ThreadLocal 对象的引用,可能会导致内存泄漏问题。确保在使用完毕后及时调用 remove() 方法或将 ThreadLocal 对象设置为 null。
-
初始值设置:每个线程在第一次访问 ThreadLocal 对象时,会调用 initialValue() 方法来获取初始值。如果需要特定的初始值,可以通过继承 ThreadLocal 并重写 initialValue() 方法来实现。
-
共享变量问题:虽然 ThreadLocal 可以在每个线程中存储自己的数据,但要注意共享变量的访问。如果多个线程共享同一个对象,并且这个对象中包含 ThreadLocal 变量,那么多个线程对 ThreadLocal 变量的修改会相互影响。确保对共享变量的访问是线程安全的。
-
使用场景选择:ThreadLocal 应该谨慎使用,只在确实需要在每个线程中存储和访问数据时使用。滥用 ThreadLocal 可能导致代码的复杂性增加,并且可能不利于代码的维护和理解。
-
清理操作:在使用 ThreadLocal 时,需要确保在合适的时机进行清理操作。当线程执行完毕或不再需要使用 ThreadLocal 时,应该手动调用 remove() 方法来清理 ThreadLocal 对象,以避免潜在的内存泄漏问题。
总之,使用 ThreadLocal 需要谨慎并遵循最佳实践,确保正确管理和使用 ThreadLocal 对象,以实现线程间的数据隔离和安全访问
标签:Java,Thread,ThreadLocalMap,ThreadLocal,深入,线程,null,local From: https://www.cnblogs.com/techs/p/17500447.html