将 ThreadLocal 与 ExecutorService 一起使用是否危险?
Is it dangerous to use ThreadLocal with ExecutorService?
我在下面的博客上了解了 ThreadLocals 的概念:
https://www.baeldung.com/java-threadlocal
它说“不要将 ThreadLocal 与 ExecutorService 一起使用”
下面是使用 ThreadLocals 的示例。
public class ThreadLocalWithUserContext implements Runnable {
private static ThreadLocal<Context> userContext
= new ThreadLocal<>();
private Integer userId;
private UserRepository userRepository = new UserRepository();
@Override
public void run() {
String userName = userRepository.getUserNameForUserId(userId);
userContext.set(new Context(userName));
System.out.println("thread context for given userId: "
+ userId + " is: " + userContext.get());
}
// standard constructor
}
在 post 的末尾提到:
If we want to use an ExecutorService and submit a Runnable to it, using ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.
Because of that, our ThreadLocal will be shared among different userIds. That’s why we should not use a TheadLocal together with ExecutorService. It should only be used when we have full control over which thread will pick which runnable action to execute.
这个解释对我来说是个保镖。我专门针对这一点尝试在网上做一些研究,但没有得到太多帮助,请高手详细解释一下上述解释?是作者的观点还是真正的威胁?
that caution 的要点是您的 Runnable
的 多个 运行 可能在不同的线程上执行。执行程序服务可以由单个线程支持,但也可以由线程池支持。在您的 Runnable
的后续执行中,不同的线程将访问不同的 ThreadLocal
。
所以您当然 可以 在 Runnable
的单个 运行 中使用 ThreadLocal
。但它不太可能有用,因为通常 ThreadLocal
的目的是暂时保留一个值。相比之下,Runnable
通常应该是短暂的。
所以,不,通常将 ThreadLocal
与线程池一起使用是没有意义的。
将 ThreadLocal 视为某种 "in memory cache",用于由同一线程执行的代码。完全相同的线程。在不同线程上执行的代码之间共享 ThreadLocal 是个坏主意。
tha javadoc 明确指出:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
换句话说:使用ThreadLocals的目标是在不同的线程"thread specific"数据中给出"each"代码运行。
另一方面,ExecutorService 首先是一个接口:您根本不知道它是由单个线程提供支持,还是(更有可能)由多个线程提供支持。
换句话说:使用 ExecutorService 很快会导致 多个不同的 线程 运行 您的 Runnables/Tasks。然后您将在这些多个线程之间共享您的 ThreadLocal。
所以,"dangerous" 可能是错误的词。使用 ThreadLocal 的 目标 是拥有每线程存储,而 ExecutorService 是关于由 未知 数量的线程执行的代码。这两件事根本不能很好地结合在一起。
侧重点不同:一个概念强调连接到非常具体的 "activity" 的长期线程。另一个概念是关于使用未知数量的无名线程执行小型独立活动。
ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.
在发布的代码示例中,上面的参数无效,因为 ThreadLocal
值是在调用 run()
时设置的,因此无论使用 ExecutorService
.
在 Runnable A
中调用 set(new Context())
然后从另一个 Runnable B
中调用 get()
是不确定的,因为您无法控制 Thread
和 Runnable
正在执行中。
假设 get()
返回的对象可以是任何东西,除非您知道它上次设置的时间。
ThreadLocal 用于在您设置 之后在该线程中缓存一个变量。这样下次想访问的时候,直接从ThreadLocal中获取即可,无需初始化。
因为你用 threadLocal.set(obj)
设置它并且你在一个线程中通过 threadLocal.get()
访问它,所以直接你有 thread-safe 保证。
但如果您不通过 threadLocal.remove()
明确清除 缓存 ,事情可能会变得很糟糕。
在线程池中,排队的任务会被一个个线程处理,大部分时间任务应该是独立的,但是thread-scope缓存threadLocal会使如果您在处理下一个任务之前忘记先清除它,则以下任务取决于其之前的任务;
cached threadLocals 不会 gc-ed 立即(在某些 unknown 时刻 - 出于你的控件),因为它们的键是 WeakReference,这可能会在你不知情的情况下导致 OOM。
针对未显式调用 remove()
导致 OOM 的情况的简单演示。
public class ThreadLocalOne {
private static final int THREAD_POOL_SIZE = 500;
private static final int LIST_SIZE = 1024 * 25;
private static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
executorService.execute(() -> {
threadLocal.set(getBigList());
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get().size());
// threadLocal.remove();
// explicitly remove the cache, OOM shall not occur;
});
}
executorService.shutdown();
}
private static List<Integer> getBigList() {
List<Integer> ret = new ArrayList<>();
for (int i = 0; i < LIST_SIZE; i++) {
ret.add(i);
}
return ret;
}
}
我在下面的博客上了解了 ThreadLocals 的概念:
https://www.baeldung.com/java-threadlocal
它说“不要将 ThreadLocal 与 ExecutorService 一起使用”
下面是使用 ThreadLocals 的示例。
public class ThreadLocalWithUserContext implements Runnable {
private static ThreadLocal<Context> userContext
= new ThreadLocal<>();
private Integer userId;
private UserRepository userRepository = new UserRepository();
@Override
public void run() {
String userName = userRepository.getUserNameForUserId(userId);
userContext.set(new Context(userName));
System.out.println("thread context for given userId: "
+ userId + " is: " + userContext.get());
}
// standard constructor
}
在 post 的末尾提到:
If we want to use an ExecutorService and submit a Runnable to it, using ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.
Because of that, our ThreadLocal will be shared among different userIds. That’s why we should not use a TheadLocal together with ExecutorService. It should only be used when we have full control over which thread will pick which runnable action to execute.
这个解释对我来说是个保镖。我专门针对这一点尝试在网上做一些研究,但没有得到太多帮助,请高手详细解释一下上述解释?是作者的观点还是真正的威胁?
that caution 的要点是您的 Runnable
的 多个 运行 可能在不同的线程上执行。执行程序服务可以由单个线程支持,但也可以由线程池支持。在您的 Runnable
的后续执行中,不同的线程将访问不同的 ThreadLocal
。
所以您当然 可以 在 Runnable
的单个 运行 中使用 ThreadLocal
。但它不太可能有用,因为通常 ThreadLocal
的目的是暂时保留一个值。相比之下,Runnable
通常应该是短暂的。
所以,不,通常将 ThreadLocal
与线程池一起使用是没有意义的。
将 ThreadLocal 视为某种 "in memory cache",用于由同一线程执行的代码。完全相同的线程。在不同线程上执行的代码之间共享 ThreadLocal 是个坏主意。
tha javadoc 明确指出:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
换句话说:使用ThreadLocals的目标是在不同的线程"thread specific"数据中给出"each"代码运行。
另一方面,ExecutorService 首先是一个接口:您根本不知道它是由单个线程提供支持,还是(更有可能)由多个线程提供支持。
换句话说:使用 ExecutorService 很快会导致 多个不同的 线程 运行 您的 Runnables/Tasks。然后您将在这些多个线程之间共享您的 ThreadLocal。
所以,"dangerous" 可能是错误的词。使用 ThreadLocal 的 目标 是拥有每线程存储,而 ExecutorService 是关于由 未知 数量的线程执行的代码。这两件事根本不能很好地结合在一起。
侧重点不同:一个概念强调连接到非常具体的 "activity" 的长期线程。另一个概念是关于使用未知数量的无名线程执行小型独立活动。
ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.
在发布的代码示例中,上面的参数无效,因为 ThreadLocal
值是在调用 run()
时设置的,因此无论使用 ExecutorService
.
在 Runnable A
中调用 set(new Context())
然后从另一个 Runnable B
中调用 get()
是不确定的,因为您无法控制 Thread
和 Runnable
正在执行中。
假设 get()
返回的对象可以是任何东西,除非您知道它上次设置的时间。
ThreadLocal 用于在您设置 之后在该线程中缓存一个变量。这样下次想访问的时候,直接从ThreadLocal中获取即可,无需初始化。
因为你用 threadLocal.set(obj)
设置它并且你在一个线程中通过 threadLocal.get()
访问它,所以直接你有 thread-safe 保证。
但如果您不通过 threadLocal.remove()
明确清除 缓存 ,事情可能会变得很糟糕。
在线程池中,排队的任务会被一个个线程处理,大部分时间任务应该是独立的,但是thread-scope缓存threadLocal会使如果您在处理下一个任务之前忘记先清除它,则以下任务取决于其之前的任务;
cached threadLocals 不会 gc-ed 立即(在某些 unknown 时刻 - 出于你的控件),因为它们的键是 WeakReference,这可能会在你不知情的情况下导致 OOM。
针对未显式调用 remove()
导致 OOM 的情况的简单演示。
public class ThreadLocalOne {
private static final int THREAD_POOL_SIZE = 500;
private static final int LIST_SIZE = 1024 * 25;
private static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
executorService.execute(() -> {
threadLocal.set(getBigList());
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get().size());
// threadLocal.remove();
// explicitly remove the cache, OOM shall not occur;
});
}
executorService.shutdown();
}
private static List<Integer> getBigList() {
List<Integer> ret = new ArrayList<>();
for (int i = 0; i < LIST_SIZE; i++) {
ret.add(i);
}
return ret;
}
}