ThreadLocal
是Java语言提供的一种线程局部变量机制,它允许你为每个线程创建变量的私有副本。这意味着每个线程都可以独立地改变自己的变量副本,而不会影响其他线程的变量副本。
ThreadLocal
的主要用途包括:
ThreadLocal
变量不需要加锁。在Java中,ThreadLocal
是通过Thread
内部的一个Map实现的,这个Map被称为ThreadLocalMap
。每个Thread
对象都含有一个ThreadLocal.ThreadLocalMap
,而ThreadLocal
对象本身作为键,线程局部变量的值作为Map中的值。
下面是ThreadLocal
一些核心方法的简化版源码:
ThreadLocal
的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();
}
当调用get()
方法时,ThreadLocal
会获取当前线程,查找当前线程的ThreadLocalMap
,并使用自身作为键来获取值。
ThreadLocal
的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);
}
}
当调用set()
方法时,如果当前线程的ThreadLocalMap
存在,就在该Map中保存一个以ThreadLocal
实例为键,用户指定值为值的条目;否则,创建该Map并添加该键值对。
ThreadLocal
的remove
方法:public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
调用remove()
方法会删除当前线程的ThreadLocalMap
中对应的条目,这对于防止内存泄漏非常重要。
下面是一个简单的ThreadLocal
使用例子:
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
System.out.println("Thread " + Thread.currentThread().getName() +
" initial value: " + threadLocalValue.get());
threadLocalValue.set((int) (Math.random() * 100));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + Thread.currentThread().getName() +
" new value: " + threadLocalValue.get());
};
Thread threadOne = new Thread(task);
Thread threadTwo = new Thread(task);
threadOne.start();
threadTwo.start();
threadOne.join();
threadTwo.join();
}
}
在这个例子中,两个不同的线程对同一个ThreadLocal
变量进行读写操作。每个线程首先打印出ThreadLocal
的初始值,然后设置一个随机值,并再次打印出来。由于使用了ThreadLocal
,尽管两个线程访问相同的threadLocalValue
变量,它们所看到的值却是彼此隔离的。
使用ThreadLocal
时,需要特别注意内存泄漏问题。由于每个线程的ThreadLocalMap
对ThreadLocal
实例的键的引用是弱引用(WeakReference),但值的引用是常规引用,如果ThreadLocal
不再被外部引用,而线程还在运行,则ThreadLocal
实例的键可能会被垃圾回收,但值却不会。如果这个值是一个重对象,或者持有对其他对象的重引用,就可能发生内存泄漏。因此,最好在不再需要使用ThreadLocal
变量时,显式调用remove()
方法来清理资源。
ThreadLocal
通过为每个线程提供一个独立的变量副本来实现线程隔离。这是通过在每个线程中维护一个称为ThreadLocalMap
的内部结构来完成的,该结构存储了线程特定的值。
ThreadLocalMap
原理ThreadLocalMap
是一个定制的哈希映射,只能由包含它的Thread
对象访问。Thread
对象中有一个称为threadLocals
的字段,这是一个指向ThreadLocalMap
的引用,但这个字段是包访问保护的,外部代码无法直接访问。
每个ThreadLocal
对象可以被视为ThreadLocalMap
的键,而与键关联的值就是线程特定的值。在实际实现中,为了避免内存泄漏,键是通过弱引用存储的,这意味着键可以被垃圾收集器回收。
ThreadLocal
方法源码在Java源码的ThreadLocal
类中,get()
和set()
方法主要负责获取和设置线程特定的值:
ThreadLocal.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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get()
方法首先获取当前线程实例,然后获取与该线程关联的ThreadLocalMap
。它使用this
(当前ThreadLocal
对象)作为键来查找相关的值。如果找到值,则返回该值。如果ThreadLocalMap
不存在或没有找到值,它将调用setInitialValue()
方法来创建并返回初始值。
ThreadLocal.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);
}
set()
方法同样获取当前线程实例,然后获取或创建ThreadLocalMap
并将值与ThreadLocal
对象关联起来。
下面是一个使用ThreadLocal
的代码示例,展示了如何为每个线程创建和维护自己的值副本:
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
final AtomicInteger nextId = new AtomicInteger(0);
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int getThreadId() {
return threadId.get();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final int taskId = i;
new Thread(() -> {
System.out.printf("Thread #%d has task id %d%n", getThreadId(), taskId);
}).start();
}
}
}
在这个例子中,我们定义了一个ThreadId
,它为每个线程生成一个唯一的ID。每个线程在开始执行时都会调用getThreadId()
,该方法返回与当前线程关联的ID,initialValue()
方法确保每个线程都有一个唯一的ID。
内存泄漏:因为ThreadLocalMap
的生命周期与它所属的线程一样长,如果ThreadLocal
变量没有被移除,而线程又一直存在(如在线程池中),ThreadLocalMap
和它的内容就不会被垃圾回收,从而可能导致内存泄漏问题。故显式调用ThreadLocal.remove()
是个好习惯。
弱引用:ThreadLocalMap
中的键是通过弱引用存储的,以允许ThreadLocal
对象被回收,而不会被ThreadLocalMap
的引用阻止。但是值并不是以弱引用存储的,因此如果值指向的对象很大,就必须确保调用remove()
来防止内存泄漏。
ThreadLocal
通过为每个线程提供一个线程局部(Thread-Local)存储空间,允许开发者为每个线程存储数据,而这些数据对其他线程而言是隔离的。ThreadLocal
实例通常是类中的私有静态字段,它们关联着与使用该变量的线程相关的值。
ThreadLocal
的基本步骤:创建 ThreadLocal
变量: 声明一个 ThreadLocal
类型的静态变量。你可以通过覆盖 initialValue
方法来为 ThreadLocal
变量提供一个初始值,或者使用 withInitial
工厂方法。
存储和访问 ThreadLocal
数据: 使用 get()
方法来访问当前线程相关联的值。如果该值尚未设置,则会返回initialValue
方法设定的值。使用 set()
方法可以设置当前线程的局部变量的值。
清理 ThreadLocal
数据: 在不再需要存储在 ThreadLocal
中的数据时,需要调用 remove()
方法来避免潜在的内存泄漏,特别是在使用线程池时。
在 ThreadLocal
的实现中,最关键的一部分就是内部类 ThreadLocalMap
,它是一个定制的哈希映射,用于存储线程局部变量的值。
ThreadLocal.ThreadLocalMap
ThreadLocalMap
是一个简化的自定义哈希表,只适用于维护线程局部变量。它使用 ThreadLocal
实例作为键,线程局部存储的对象作为值。
ThreadLocal.set(T value)
方法set()
方法将当前 ThreadLocal
实例与值相关联在当前线程的 ThreadLocalMap
中:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocal.get()
方法get()
方法用于获取与当前线程相关联的 ThreadLocal
实例的值:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T)e.value;
}
}
return setInitialValue();
}
如果当前线程的 ThreadLocalMap
不存在或者没有找到对应的值,setInitialValue()
会被调用以初始化值。
public class ThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> {
String formattedDate = dateFormatThreadLocal.get().format(new Date());
System.out.println("Thread: " + Thread.currentThread().getName() + ", Formatted Date: " + formattedDate);
};
// 启动两个线程,执行任务
executor.submit(task);
executor.submit(task);
// 关闭 ExecutorService 并等待其终止
executor.shutdown();
}
}
在这个例子中,我们创建了一个 ThreadLocal
实例,用于存储 SimpleDateFormat
对象。每个线程将获取自己的日期格式化实例,并使用它来格式化当前日期。在多线程环境中,使用 ThreadLocal
可以确保每个线程都有自己的 SimpleDateFormat
实例,从而避免了线程安全问题。
ThreadLocal
的 initialValue()
方法在第一次调用 get()
时被触发,如果尚未调用 set()
,则用于提供初始值。remove()
方法清理 ThreadLocal
是一个很好的习惯,以免导致内存泄漏。ThreadLocal
不是用来解决共享对象的多线程访问问题的,而是为了提供线程内部的私有存储。对于多线程访问共享资源的同步控制,应该使用其他同步机制,比如 synchronized
、ReentrantLock
等。ThreadLocal
可以减少对于某些需要线程安全的对象的同步需求(如上例中的 SimpleDateFormat
),因为每个线程都有自己的实例,从而提高执行效率。ThreadLocal
类在 Java 中提供了一组设计用来操作线程局部变量的方法。这些方法可以分为几个类别:初始化、访问、修改和清除线程局部变量。
initialValue()
:这是一个受保护的方法,通常由你继承 ThreadLocal
并覆盖该方法来供 get()
方法在首次访问线程局部变量时使用,以便提供一个初始值。protected T initialValue() {
return null;
}
withInitial(Supplier<? extends S> supplier)
:这是一个静态工厂方法,Java 8 引入,允许你通过传递一个 Supplier
函数式接口的实例来创建一个 ThreadLocal
对象,并且用 Supplier
提供的值作为初始值。public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
get()
:返回与当前线程关联的此线程局部变量的值。如果变量没有初始化,则会调用 initialValue()
方法来进行初始化。public T get() {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T)e.value;
}
}
return setInitialValue();
}
set(T value)
:将当前线程的此线程局部变量的副本设置为指定的值。public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
remove()
:移除当前线程的此线程局部变量的值。public void remove() {
ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
下面是如何使用 ThreadLocal
的一个实例,演示了如何为每个线程存储和访问一个唯一的用户ID。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalExample {
private static final ThreadLocal<Integer> userIdThreadLocal = ThreadLocal.withInitial(new AtomicInteger()::getAndIncrement);
public static void main(String[] args) {
Runnable task = () -> {
int userId = userIdThreadLocal.get();
System.out.println("Thread " + Thread.currentThread().getName() + " User ID: " + userId);
};
// 启动三个线程
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
这个例子中每次运行任务,userIdThreadLocal.get()
都会返回一个唯一的用户ID,这是因为我们利用 AtomicInteger
提供了一个线程安全的自增操作。
在使用 ThreadLocal
方法时需要注意几个重要点:
内存泄漏问题:每个线程都有一个对应的 ThreadLocal.ThreadLocalMap
实例,如果线程一直运行着,而且我们没有及时调用 remove()
方法,那么由于 ThreadLocalMap
对这些局部变量的强引用,可能会导致内存泄漏。这在使用线程池时尤其重要,因为线程通常会被重用。
初始值:initialValue()
或 withInitial(Supplier<? extends S> supplier)
方法可以确保每个线程都有自己的变量初始值。如果没有提供这些方法,线程局部变量的初始值将为 null
。
线程局部变量的数据隔离:ThreadLocal
变量确保数据的线程隔离性,这意味着每个线程都能独立地操作自己的数据副本,不受其他线程的影响。
类型安全:ThreadLocal
是一个泛型类,使得线程局部变量是类型安全的。
总之,ThreadLocal
是管理线程局部变量的强大工具,但也应该要注意其使用方式,避免潜在的内存泄漏问题。
清理 ThreadLocal
资源是防止内存泄漏的重要步骤。由于 ThreadLocal
在每个线程中为变量维护单独的副本,如果这些线程是长时间运行的,或者是线程池中的线程,那么这些变量可能会在不再需要它们之后仍然存活,造成内存泄漏。以下是如何清理 ThreadLocal
资源的详细说明:
ThreadLocal
资源释放流程使用 ThreadLocal.remove()
方法:
当你知道不再需要某个线程局部变量时,应该调用 ThreadLocal
实例的 remove()
方法来移除当前线程的这个变量副本。该操作会从当前线程的 ThreadLocalMap
中移除对应的条目。
源码解析:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
该方法首先取得当前线程的 ThreadLocalMap
,然后调用 remove()
方法删除当前 ThreadLocal
实例的键(及其对应的值)。
确保线程结束时清理:
在使用完 ThreadLocal
变量后,应该尽早调用 remove
。这在使用线程池的场景下尤为重要,因为线程池中的线程通常不会结束,而是被重用,所以它们引用的 ThreadLocal
变量可能不会自动被垃圾回收。
ThreadLocal
资源的示例public class ThreadLocalCleanupExample {
private static final ThreadLocal<SimpleDateFormat> dateFormatter = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
@Override
public void remove() {
super.remove();
System.out.println("DateFormatter removed for Thread: " + Thread.currentThread().getName());
}
};
public static void main(String[] args) {
Runnable task = () -> {
String dateStamp = dateFormatter.get().format(new Date());
System.out.println("Thread: " + Thread.currentThread().getName() + ", Date Stamp: " + dateStamp);
// 在线程逻辑的最后调用 remove 来清理
dateFormatter.remove();
};
ExecutorService executorService = Executors.newFixedThreadPool(3);
try {
// 执行任务
executorService.execute(task);
executorService.execute(task);
executorService.execute(task);
} finally {
executorService.shutdown();
}
}
}
在上面的示例中,我们首先定义了一个 ThreadLocal
变量 dateFormatter
,然后在多线程的环境下使用它。在每个线程执行的任务的最后,我们调用了 dateFormatter.remove()
来清理每个线程中的 ThreadLocal
资源。
明确 remove
调用时机: 最佳实践是,在每次使用完 ThreadLocal
变量之后立即调用 remove()
方法。如果该变量是在 try
块中使用的,那么 remove()
应该在 finally
块中调用以确保它总是执行。
防止内存泄漏: 如果不在每个线程结束时清理线程局部变量,尤其是在线程池场景中,ThreadLocal
变量可能会导致内存泄漏,因为 ThreadLocalMap
的生命周期与线程一样长。
了解 ThreadLocal
内部机制: ThreadLocal
使用 Thread
对象中的 threadLocals
字段来存储每个线程的 ThreadLocalMap
。每个 ThreadLocalMap
使用线程安全的方法存储和检索键值对。
通过遵循这些步骤和注意事项,你可以确保在使用 ThreadLocal
时及时清理资源,避免内存泄漏,并养成一个良好的编程习惯。
ThreadLocal
提供了一个方便的方式来在每个线程中管理数据的隔离性,但是它也有一些缺点和局限性:
ThreadLocal
在线程的生命周期中持有变量,如果在使用完后不显式地调用 ThreadLocal.remove()
方法来清除这些变量,它们可能会一直存活在 JVM 的内存中,尤其是在使用线程池时,因为线程池中的线程通常会被重用而不是结束。
这是因为每个 Thread
对象都有一个 ThreadLocal.ThreadLocalMap
的引用,它使用 ThreadLocal
对象作为键,存储了线程局部变量的值。由于 ThreadLocalMap
使用 ThreadLocal
对象的弱引用作为键,所以即使 ThreadLocal
对象不再被引用,它的键可以被垃圾收集器回收。但是,值不是弱引用,如果没有显式调用 remove()
方法,值就不能被回收。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// ...
}
每个线程创建时都会创建与之关联的 ThreadLocalMap
,这就意味着如果系统创建了大量的线程,每个线程又使用了大量的 ThreadLocal
变量,那么管理这些额外的数据结构会增加额外的内存负担。
因为 ThreadLocal
变量对于它们所在的线程是私有的,它们可以在整个线程的执行过程中任何地方被访问,这可能导致代码的维护和理解变得复杂,尤其是在大型项目和团队中。
使用 fork/join
框架或者 java.util.stream
中的并行流时,ThreadLocal
变量可能会引起混乱,因为这些并发框架可能会将任务分配给线程池中的任何线程,而这些线程可能已经携带了一些 ThreadLocal
变量的值,从而使得数据的状态变得不可预测。
public class ThreadLocalDisadvantages {
private static final ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
public static void main(String[] args) {
threadLocalValue.set(1);
new Thread(() -> {
// 没有调用threadLocalValue.set(),但是可能获取到错误的值
// 如果在其他地方有修改threadLocalValue,结果是不可预测的
System.out.println(threadLocalValue.get()); // 可能打印null
}).start();
// ... 执行其他操作
// 忘记调用 remove(),可能导致内存泄漏
// threadLocalValue.remove(); // 应该显式调用
}
}
在这个示例中,如果忘记调用 threadLocalValue.remove()
,那么随着线程的结束,由于没有及时清理资源,将可能导致内存泄漏。
在考虑使用 ThreadLocal
时,你应该明白:
ThreadLocal
变量,以防内存泄漏。ThreadLocal
变量的数量,以减轻对内存的压力。ThreadLocal
,并且注意它可能会使你的代码更难维护。CompletableFuture
、parallelStream
等并发工具时,谨慎使用 ThreadLocal
,因为这些工具可能会与 ThreadLocal
变量产生不一致的行为。合理使用 ThreadLocal
确实可以解决特定的问题,但也应该意识到它的缺点并采取措施避免相关问题。
ThreadLocal
和同步机制(如 synchronized
和 Lock
)都用于多线程编程环境,但它们解决的问题和使用方式有很大的不同。
ThreadLocal
用来为每个线程提供各自的变量副本,确保线程之间的数据隔离。每个线程都可以以线程安全的方式访问其内部 ThreadLocalMap
中的变量,而无需进行同步,因为这些变量对其他线程来说是不可见的。
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadId =
ThreadLocal.withInitial(() -> Thread.currentThread().hashCode());
public static void main(String[] args) {
new Thread(() -> {
System.out.println("Thread A ID: " + threadId.get()); // 线程A的独立ID
}).start();
new Thread(() -> {
System.out.println("Thread B ID: " + threadId.get()); // 线程B的独立ID
threadId.remove();
}).start();
}
}
在这里,每个线程调用 threadId.get()
时都会得到一个与其他线程不同的值。由于使用的是 ThreadLocal
变量,无需担心多线程访问冲突,因此不使用同步机制。
同步机制(如 synchronized
和 Lock
)是为了在多个线程之间安全地共享资源。当多个线程想要访问同一个资源时,同步机制确保同一时间只有一个线程可以访问资源,防止并发问题如数据竞争和条件竞争。
public class SynchronizedExample {
private static int sharedState = 0;
public static synchronized void increment() {
sharedState++;
}
public static void main(String[] args) {
Thread threadA = new Thread(SynchronizedExample::increment);
Thread threadB = new Thread(SynchronizedExample::increment);
threadA.start();
threadB.start();
}
}
在上面的代码中,increment()
方法是 synchronized
的,这意味着同一时间只有一个线程能够修改 sharedState
。
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private static final ReentrantLock lock = new ReentrantLock();
private static int sharedState = 0;
public static void increment() {
lock.lock();
try {
sharedState++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Thread threadA = new Thread(LockExample::increment);
Thread threadB = new Thread(LockExample::increment);
threadA.start();
threadB.start();
}
}
在 Lock
示例中,我们使用 ReentrantLock
明确地管理锁定和解锁过程,以确保 sharedState
变量的线程安全。
ThreadLocal
是为了隔离数据,让每个线程有自己的数据副本。同步机制是为了保护数据,不允许多个线程同时修改同一份数据。ThreadLocal
。如果你需要协调多个线程对共享资源的访问,你应该使用同步机制。ThreadLocal
可能会导致更高的内存消耗,但通常不会引起线程阻塞。同步机制可能会引起线程竞争、阻塞和上下文切换,这可能对性能有显著影响。在选择使用 ThreadLocal
还是同步机制时,你应该考虑以下因素:
ThreadLocal
可能带来的额外内存消耗?通过理解这些关键差异并根据实际的应用场景和需求作出选择,可以更有效地使用这些工具来编写线程安全的代码。
ThreadLocal
和 InheritableThreadLocal
都是用来提供线程范围内的变量存储,但是它们在处理线程之间的变量继承方面存在差异。
ThreadLocal
为每个使用该变量的线程提供了一个独立初始化的变量副本。每个线程只能访问和修改自己的副本,而不会影响其他线程中的副本。这对于保存线程私有的状态非常有用。
public class ThreadLocal<T> {
// ...
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
使用 ThreadLocalMap
,是 Thread
对象的一个内部类,它使用 ThreadLocal
实例本身作为键来存储值。
InheritableThreadLocal
扩展了 ThreadLocal
,提供了一种新的线程局部变量,它不仅可以被线程本身访问,还可以被该线程派生出的子线程访问。当一个线程创建一个新的子线程时,InheritableThreadLocal
会将父线程中的值复制到子线程中。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// ...
protected T childValue(T parentValue) {
return parentValue;
}
// ...
}
当一个新的线程被创建时,Java 虚拟机会调用 Thread
类的构造函数,这个过程中会检查父线程的 inheritableThreadLocals
字段,并将其通过 childValue
方法复制到新创建的线程。
Thread parentThread = new Thread(() -> {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("value");
Thread childThread = new Thread(() -> {
// 子线程可以访问由父线程设置的inheritableThreadLocal的值
System.out.println("Child thread value: " + inheritableThreadLocal.get()); // 输出 "value"
});
childThread.start();
});
parentThread.start();
子线程将输出由父线程设置在 InheritableThreadLocal
上的值。
ThreadLocal
不会将值传递给任何派生的子线程,而 InheritableThreadLocal
设计用来做到这一点。InheritableThreadLocal
。相比之下,如果你想要确保每个线程的数据是完全隔离的,那么 ThreadLocal
是更合适的选择。remove()
方法进行清理。InheritableThreadLocal
的复制操作可能会带来额外的性能开销,尤其是当创建大量线程时。在使用 InheritableThreadLocal
时,你应该注意:
InheritableThreadLocal
中的值在不同任务之间产生混淆。总结来说,ThreadLocal
和 InheritableThreadLocal
分别适用于不同的场景,开发者应该根据具体的需求和线程模型来选择使用哪一个。同时,要注意它们的使用需要谨慎管理,以避免内存泄漏等问题。
使用 ThreadLocal
可以让每个线程都拥有自己的变量副本,但如果不恰当地使用,可能会引发内存泄漏等问题。以下是使用 ThreadLocal
的一些最佳实践:
在不再需要存储在 ThreadLocal
中的数据时,应当显式调用 ThreadLocal.remove()
来清理资源。这是因为 ThreadLocal
可能会导致长生命周期的线程(例如线程池中的线程)持续持有对放在 ThreadLocal
中对象的引用,从而引发内存泄漏。
ThreadLocal<MyObject> myThreadLocal = new ThreadLocal<>();
try {
myThreadLocal.set(new MyObject());
// 执行一些操作
} finally {
myThreadLocal.remove();
}
在上述示例中,确保在 finally
块中调用 .remove()
以避免内存泄漏。
ThreadLocal
变量的作用域通常情况下,ThreadLocal
变量应该是 private static
的,以减少对这些变量的可见性,并避免在多个类或对象之间共享它们。
withInitial
工厂方法Java 8 引入的 ThreadLocal.withInitial(Supplier<? extends S> supplier)
工厂方法可以方便地设置初始值。
private static final ThreadLocal<DateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
ThreadLocal
中存储大型对象由于 ThreadLocal
数据是与线程绑定的,存储大型对象可能会导致内存消耗过大,特别是在使用大量线程的应用程序中。
InheritableThreadLocal
使用时注意线程池问题如果你正在使用 InheritableThreadLocal
和线程池,请注意,由于线程池线程的复用,设置的值可能会在多次任务执行间意外共享。
ThreadLocal
映射替代多个 ThreadLocal
变量如果你需要在一个线程中存储多个线程局部变量,可以考虑使用单个 ThreadLocal
,其中存储一个映射(Map)来存放多个键值对。
ThreadLocal
的工作原理深入理解 ThreadLocal
的实现,可以帮助更好地使用它。每个线程持有一个 ThreadLocal.ThreadLocalMap
的引用,而 ThreadLocalMap
使用线性探测哈希映射(linear probing hash map)来解决键的冲突。
ThreadLocalMap
源码关键部分static class ThreadLocalMap {
// Entry 继承自 WeakReference,用于引用 thread-local 变量的键
static class Entry extends WeakReference<ThreadLocal<?>> {
// 值不是弱引用
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// ...
}
ThreadLocal
在 servlet 环境中在 servlet 环境中使用 ThreadLocal
是需要特别注意的,因为 web 容器可能会使用线程池,这意味着线程可能在处理多个请求之间被重用。确保在请求处理完成之后清理 ThreadLocal
变量。
ThreadLocal
是一个强大的工具,可以帮助解决特定的线程隔离问题。然而,如果不遵守上述最佳实践,它可能会导致严重的资源管理问题,如内存泄漏。正确地使用 ThreadLocal
可以确保你的应用程序的性能和可维护性。