为了账号安全,请及时绑定邮箱和手机立即绑定

将 ThreadLocal 与 ExecutorService 一起使用是否危险?

将 ThreadLocal 与 ExecutorService 一起使用是否危险?

慕容708150 2022-06-23 17:44:56
我在下面的博客中讨论了 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}在帖子的末尾,它提到:如果我们想使用 ExecutorService 并向其提交 Runnable,使用 ThreadLocal 将产生不确定的结果——因为我们不能保证给定 userId 的每个 Runnable 操作在每次执行时都会由同一个线程处理.因此,我们的 ThreadLocal 将在不同的 userId 之间共享。这就是为什么我们不应该将 TheadLocal 与 ExecutorService 一起使用。只有当我们完全控制哪个线程将选择执行哪个可运行操作时才应该使用它。这个解释对我来说是一个保镖。我试图专门针对这一点在网上做一些研究,但我没有得到太多帮助,请高手详细说明一下上述解释吗?是作者的观点还是真正的威胁?
查看完整描述

4 回答

?
ABOUTYOU

TA贡献1812条经验 获得超5个赞

将 ThreadLocal 视为由同一线程执行的代码的某种“内存缓存”。完全相同的线程。在不同线程上执行的代码之间共享 ThreadLocal 是一个坏主意。

Tha javadoc 明确指出:

此类提供线程局部变量。这些变量不同于它们的正常对应变量,因为每个访问一个(通过它的 get 或 set 方法)的线程都有它自己的、独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)。

换句话说:使用 ThreadLocals 的目标是为在不同线程中运行的“每个”代码提供“线程特定”数据。

另一方面,ExecutorService 首先是一个接口:您根本不知道它是由单个线程提供支持,还是(更可能)由多个线程提供支持。

换句话说:使用 ExecutorService 会很快导致多个不同的线程运行您的 Runnables/Tasks。然后您将在这些多个线程中共享您的 ThreadLocal。

因此,“危险”可能是错误的词。使用 ThreadLocal的目标是拥有每个线程的存储,而 ExecutorService 是关于由未知数量的线程执行的代码。这两件事根本不能很好地结合在一起。

重点是不同的:一个概念强调与非常具体的“活动”相关的长期存在的线索。另一个概念是关于使用未知数量的无名线程执行小型独立活动。


查看完整回答
反对 回复 2022-06-23
?
湖上湖

TA贡献2003条经验 获得超2个赞

需要注意的是,您的多次运行Runnable可能会在不同的线程上执行。执行器服务可以由单个线程支持,但也可以由线程池支持。在您的后续执行中Runnable,不同的线程将访问不同的ThreadLocal.

因此,您当然可以ThreadLocalRunnable但它不太可能有用,因为通常 a 的目的ThreadLocal是保持一个值一段时间。相反,aRunnable通常应该是短暂的。

所以,不,通常将 aThreadLocal与线程池一起使用是没有意义的。


查看完整回答
反对 回复 2022-06-23
?
慕桂英546537

TA贡献1848条经验 获得超10个赞

ThreadLocal 将产生不确定的结果——因为我们不能保证给定 userId 的每个 Runnable 操作在每次执行时都由同一个线程处理。

在发布的代码示例中,上面的参数是无效的,因为该ThreadLocal值是在run()被调用时设置的,因此同一块中的任何后续get()都是确定性的,无论使用ExecutorService.

然后从另一个调用不是确定性set(new Context())的,因为您无法控制正在执行的。Runnable Aget()Runnable BThreadRunnable

假设返回的对象get()可以是任何东西,除非你知道它最后一次设置的时间。


查看完整回答
反对 回复 2022-06-23
?
慕码人2483693

TA贡献1860条经验 获得超9个赞

ThreadLocal 用于在设置变量在该线程中缓存变量。所以下次你想访问它时,你可以直接从 ThreadLocal 中获取它,而无需初始化。

由于您设置它threadLocal.set(obj)并通过线程内访问它threadLocal.get(),因此您直接获得线程安全保证。

但是,如果您不明确清除缓存,事情可能会变得丑陋threadLocal.remove()

  1. 在线程池中,排队的任务会被线程一一处理,大部分时间任务应该是独立的,但是线程范围缓存threadLocal如果忘记清除它会使后面的任务依赖于它之前的任务首先在处理下一个任务之前;

  2. 缓存的threadLocals 不会立即被 gc-ed(在某个未知的时刻 - 超出您的控制),因为它们的键是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;

    }

}


查看完整回答
反对 回复 2022-06-23
  • 4 回答
  • 0 关注
  • 246 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号