ThreadLocal是什么?
ThreadLocal是Java中的一个类,用于创建线程局部变量和解决线程安全。每个线程都有自己独立的变量副本,彼此之间互不影响。它的主要作用是在多线程环境下,确保每个线程都有自己的变量实例,避免了变量共享带来的线程安全问题。
ThreadLocal 的主要功能
- 线程局部变量:每个线程都有自己的变量副本,互不干扰。
- 线程安全:避免了多线程环境下的竞争和冲突。
ThreadLocal 的核心方法
Thread.currentThread()
获取当前线程。getMap(t)
获取当前线程的ThreadLocalMap
。map.getEntry(this)
从ThreadLocalMap
中获取以当前ThreadLocal
为键的条目(Entry
)。- 如果条目存在,返回其值;否则调用
setInitialValue()
进行初始化。
set(T value)
set(T value)
方法用于设置当前线程的局部变量值,具体实现如下:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
Thread.currentThread()
获取当前线程。getMap(t)
获取当前线程的ThreadLocalMap
。- 如果
ThreadLocalMap
存在,调用map.set(this, value)
设置值;否则调用createMap(t, value)
创建一个新的ThreadLocalMap
。
remove()
remove()
方法用于移除当前线程的局部变量值,具体实现如下:
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
- 获取当前线程的
ThreadLocalMap
。 - 如果
ThreadLocalMap
存在,调用m.remove(this)
移除条目。
ThreadLocal 的典型使用场景
- 用户会话管理:在web应用中,存储与当前线程(用户请求)相关的信息,如用户会话、请求上下文等。
- 数据库连接:为每个线程分配独立的数据库连接,避免连接共享带来的线程安全问题。
- 事务管理:存储与当前线程相关的事务信息,如事务状态、事务ID等。
- 格式化工具:存储与当前线程相关的工具实例,如SimpleDateFormat,避免工具共享带来的线程安全问题。
ThreadLocal 的实现原理
ThreadLocal
是 Java 中用于实现线程局部变量的类,它为每个线程提供一个独立的变量副本。实现这一点的关键在于每个线程都有一个 ThreadLocalMap
对象,ThreadLocalMap
类似于一个哈希表,存储了当前线程所对应的所有 ThreadLocal
变量及其值。理解 ThreadLocal
的实现原理,需要深入探讨其核心机制及内部结构。以下是详细的解释:
1. ThreadLocal
类的基本结构
ThreadLocal
类本身非常简单,主要包含以下几个重要的方法:
get()
: 获取当前线程的局部变量值。set(T value)
: 设置当前线程的局部变量值。remove()
: 移除当前线程的局部变量值。
这些方法都依赖于每个线程独有的 ThreadLocalMap
。
2. ThreadLocalMap
ThreadLocalMap
是 ThreadLocal
的一个静态内部类,它是一个自定义的哈希表,用于存储线程局部变量。每个线程都有一个 ThreadLocalMap
实例,存储在线程对象中。其实现机制如下:
- 存储位置:
ThreadLocalMap
存储在每个线程的Thread
对象中,具体来说,ThreadLocalMap是Thread
类的一个实例变量:
public class Thread {// 部分源码省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;// 部分源码省略...
}
- 键和值:
ThreadLocalMap
的键是ThreadLocal
对象,值是实际存储的数据,类型为Object。为了避免ThreadLocal
对象的内存泄漏,ThreadLocalMap的键使用的是弱引用(WeakReference<ThreadLocal<?>>
)。
3. ThreadLocalMap
的内部结构
ThreadLocalMap
是一个自定义的哈希表,主要包含以下结构:
- Entry:
ThreadLocalMap
的内部静态类,用于存储键值对。键是弱引用的ThreadLocal
对象,值是实际数据。而Entry是以数组的形式存在,在源码中的体现就是Entry数组成员变量table(private Entry[] table;),也就是说每个ThreadLocalMap可以保存多个ThreadLocal作为键,值可以设置为任意你想与该ThreadLocal进行关联的值()。
/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread. To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/
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;/*** The next size value at which to resize.*/private int threshold;// 部分源码省略...
}
- 哈希冲突处理:
ThreadLocalMap
使用线性探测法解决哈希冲突。当发生哈希冲突时,会依次检查下一个位置,直到找到空位置或匹配的键。 - 垃圾回收:由于键是弱引用,当
ThreadLocal
对象没有其他强引用时,会被垃圾回收器回收。此时,ThreadLocalMap
的键会变成null
,但值仍然存在。因此,需要显式地调用remove()
方法或依赖ThreadLocalMap
的内部机制来清理这些条目。
4.Thread
的成员变量ThreadLocalMap
每个线程 (Thread 对象) 都有一个 ThreadLocalMap 实例。具体来说,Thread 类有一个 ThreadLocalMap类型的threadLocals 字段,用于保存当前线程的 ThreadLocalMap, 也就是负责管理当前线程的变量副本。因为ThreadLocalMap可以保存多个不同的ThreadLocal对象作为键,值为任意内容的键值对,所以每个线程可以保存多个变量副本,数量上限取决于ThreadLocal对象的个数。
public
class Thread implements Runnable {// 部分源码省略.../* 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;// 部分源码省略...
}
5. 如何为每个线程提供一个独立的变量副本,实现线程安全?
在多线程环境中, 每个线程操作 ThreadLocal 类时,只影响到自己线程的 ThreadLocalMap 里面的内容,而不会干扰到其他线程的 ThreadLocalMap。 如果应用程序中只使用一个ThreadLocal,那么每个线程内部的 ThreadLocalMap 都保存着相同的 ThreadLocal对象作为键,值可以设置成任意内容。如果应用程序中使用了多个ThreadLocal,那么每个线程内部的 ThreadLocalMap 都保存着多个 ThreadLocal对象 作为键,值为 每个ThreadLocal对象所关联对应的内容,每个线程也就保存了多个独立的变量副本。因此也就实现了每个线程都有自己的独立变量副本。
接下来我们看看ThreadLocal的set()方法和get()方法源码
get()方法
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);// 如果当前线程的ThreadLocalMap不为nullif (map != null) {// 从ThreadLocalMap的Entry类型数组table中获取以当前// ThreadLocal作为键的Entry实例ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {// Entry实例e不为null,返回其value@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 给当前线程t的ThreadLocalMap设置初始值return setInitialValue();
}/*** Get the entry associated with key. This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss. This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param key the thread local object* @return the entry associated with key, or null if no such*/
private Entry getEntry(ThreadLocal<?> key) {// 计算索引值,用于在table中定位Entryint i = key.threadLocalHashCode & (table.length - 1);// 获取当前位置的EntryEntry e = table[i];// 检查当前位置的Entry是否有效且其存储的ThreadLocal对象等于keyif (e != null && e.get() == key) {// 直接命中,返回Entryreturn e;} else {// 未命中或Entry已失效,则通过getEntryAfterMiss进一步处理return getEntryAfterMiss(key, i, e);}
}
set()方法
/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.
*/
public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)//如果当前线程的ThreadLocalMap不为null// 设置key为当前ThreadLocal的值为valuemap.set(this, value);else//如果当前线程的ThreadLocalMap为nullcreateMap(t, value);//初始化一个ThreadLocalMap,键为当前ThreadLocal
}// .../*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
6. 内存泄露问题
由于ThreadLocalMap
的键是弱引用,ThreadLocal
对象被回收后,键会变成null
,但值仍然保留在内存中,导致内存泄露。因此,建议在不再使用 ThreadLocal
时显式调用 remove()
方法,以确保清理数据。
7. 总结
每个线程在使用 ThreadLocal 类时,操作的是自己线程内部的 ThreadLocalMap,这确保了线程之间的隔离:
- 独立性:每个线程拥有自己的 ThreadLocalMap 实例,因此对 ThreadLocal 的操作不会相互干扰。
- 线程安全:由于每个线程有独立的 ThreadLocalMap,不存在并发访问 ThreadLocalMap 的问题,因此操作是线程安全的。
这种设计使得 ThreadLocal 非常适合在多线程环境下使用,用于存储线程私有的变量,从而避免了线程间的数据共享问题。