1. 写在前面
这篇文章我们一起来看下日常工作中使用比较多的ThreadLocal,我先抛出几个问题,大家一起思考下:
- ThreadLocal的主要作用是什么?
- ThreadLocal怎么使用?
- ThreadLocal 的底层实现原理是什么?
- ThreadLocal 如何避免内存泄漏?
- 在使用线程池时,ThreadLocal 有哪些注意事项?
- ThreadLocal 和 synchronized 的区别是什么?
- InheritableThreadLocal 是什么?它与 ThreadLocal 有什么区别?
- ThreadLocal 的 initialValue 方法有什么作用?
- ThreadLocal 的应用场景有哪些?
2. 作用
ThreadLocal 是 Java 中用于创建线程本地变量的类。每个线程都有自己独立的变量副本,互不干扰。主要作用如下:
- 线程安全性:通过为每个线程提供独立的变量副本,避免了多线程并发访问同一个变量时的竞争问题。
- 简化代码:在某些场景下,使用 ThreadLocal 可以避免复杂的同步机制,使代码更加简洁和易读。
- 状态隔离:适用于需要在多个方法或类之间共享线程状态的场景,例如数据库连接、用户会话等。
3. 使用示例
以下是一个简单的示例,展示如何使用 ThreadLocal:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
int value = threadLocalValue.get();
value += 1;
threadLocalValue.set(value);
System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
在这个示例中,每个线程都有自己独立的 threadLocalValue 副本,互不干扰。
4. 底层实现原理
- ThreadLocal类:ThreadLocal 类本身并不存储实际的变量值,而是通过 ThreadLocalMap 来存储
- ThreadLocalMap类:每个线程内部都有一个 ThreadLocalMap 对象,用于存储所有的 ThreadLocal 变量。
- Entry类:ThreadLocalMap 使用一个内部静态类 Entry 来存储键值对,其中键是 ThreadLocal 对象,值是实际的变量值。
4.1 主要方法
- set(T value):将当前线程的 ThreadLocal 变量设置为指定的值。
- get():获取当前线程的 ThreadLocal 变量的值。
- remove():删除当前线程的 ThreadLocal 变量。
4.2 主要数据结构
4.2.1 ThreadLocal
public class ThreadLocal<T> {
// 每个ThreadLocal对象都有一个唯一的threadLocalHashCode
private final int threadLocalHashCode = nextHashCode();
// 获取当前线程的ThreadLocalMap
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变量
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 获取当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
4.2.2 ThreadLocalMap
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
// 获取Entry
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);
}
// 设置Entry
private void set(ThreadLocal<?> key, Object value) {
int i = key.threadLocalHashCode & (table.length - 1);
for (Entry e = table[i];
e != null;
e = table[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
}
table[i] = new Entry(key, value);
}
}
5. ThreadLocal 如何避免内存泄漏?
ThreadLocalMap 使用 WeakReference 来引用 ThreadLocal 对象。当 ThreadLocal 对象被垃圾回收后,ThreadLocalMap 中的 Entry 仍然存在,可能导致内存泄漏。因此,建议在使用完 ThreadLocal 变量后调用 remove() 方法,以清除线程局部变量,避免内存泄漏。
6. ThreadLocal 和 synchronized 的区别是什么?
- ThreadLocal:提供线程本地变量,每个线程都有自己的独立副本,不需要同步,适用于需要在多个方法或类之间共享线程状态的场景。
- synchronized:提供同步机制,确保同一时间只有一个线程可以访问被同步的代码块或方法,适用于需要多个线程访问同一个共享资源的场景。
7. 如何实现一个简单的 ThreadLocal 类?
public class SimpleThreadLocal<T> {
private Map<Thread, T> threadLocalMap = new ConcurrentHashMap<>();
public void set(T value) {
threadLocalMap.put(Thread.currentThread(), value);
}
public T get() {
return threadLocalMap.get(Thread.currentThread());
}
public void remove() {
threadLocalMap.remove(Thread.currentThread());
}
}
这个简单实现使用 ConcurrentHashMap 存储每个线程的变量副本。
8. InheritableThreadLocal 是什么?它与 ThreadLocal 有什么区别?
InheritableThreadLocal 是 ThreadLocal 的一个子类,它允许子线程继承父线程的线程本地变量。与 ThreadLocal 不同,ThreadLocal 变量在创建子线程时不会被继承。
9. ThreadLocal 的 initialValue 方法有什么作用?
initialValue 方法返回当前线程的 ThreadLocal 变量的初始值。可以通过重写 initialValue 方法来提供线程本地变量的默认值。Java 8 引入了 withInitial 方法,可以更方便地设置初始值。
ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);