将 InheritableThreadLocal 与 ThreadPoolExecutor - 或 - 不重用线程的 ThreadPoolExecutor 一起使用

Posted

技术标签:

【中文标题】将 InheritableThreadLocal 与 ThreadPoolExecutor - 或 - 不重用线程的 ThreadPoolExecutor 一起使用【英文标题】:Using InheritableThreadLocal with ThreadPoolExecutor -- or -- a ThreadPoolExecutor that doesn't reuse threads 【发布时间】:2012-02-19 04:58:20 【问题描述】:

我正在尝试同时使用InheritableThreadLocalThreadPoolExecutor

这是因为ThreadPoolExecutor 为每个池重用线程(毕竟它是一个池),这意味着InheritableThreadLocal 不能按预期工作。现在这个问题对我来说似乎很明显,但追踪起来特别难。

我使用InheritableThreadLocal,这样几个***进程中的每一个都有自己的数据库连接以及它产生的任何子进程。我不只使用一个共享连接池,因为每个***进程在提交到数据库和/或准备大量反复使用的 PreparedStatement 之前都会对其连接执行大量多步骤工作。

我在这些***进程之间使用共享的ThreadPoolExecutor,因为有些行为需要被限制。例如即使我可能有 4 个***进程正在运行,但我一次只能让任何一个进程写入数据库(或者系统需要对其他共享资源进行门控)。因此,我将让***进程创建一个 Runnable 并将其发送到共享的 ThreadPoolExecutor 以确保不超过一个(或两个或三个,视情况而定)同时运行整个系统。

问题在于,因为ThreadPoolExecutor 将其线程重用于池,InheritableThreadLocal 正在获取在该池中运行的原始值,而不是在发送可运行到ThreadPoolExecutor

有什么方法可以强制ThreadPoolExecutor 中的工作池使用在创建 Runnable 的进程上下文中的 InheritableThreadLocal 值,而不是在重用线程池的上下文中?

或者,是否有任何ThreadPoolExecutor 的实现在每次启动新的 Runnable 时创建一个新线程?出于我的目的,我只关心将同时运行的线程数控制为固定大小。

人们是否有任何其他解决方案或建议可以让我完成我上面描述的事情?

(虽然我意识到我可以通过在类到类到子线程到子线程之间传递数据库连接来解决问题,就像某种社区自行车一样,但我想避免这种情况。)

*** 上有一个先前的问题InheritableThreadLocal and thread pools,也解决了这个问题。但是,该问题的解决方案似乎是 InheritableThreadLocal 的用例很差,我认为这不适用于我的情况。

感谢您的任何想法。

【问题讨论】:

【参考方案1】:

不使用 ThreadPoolExecutor 来保护共享资源,为什么不使用java.util.concurrent.Semaphore?您创建的子任务将在它们自己的线程中运行完成,但只有在从信号量获得许可之后,当然在完成后释放许可。

【讨论】:

我没有使用过信号量,所以我会研究一下。我们使用了执行器的许多其他特性(期货、超时等),但我可以使用信号量来做到这一点,或者我可以使用常规执行器(不是线程池)和信号量来获得我需要的一切。谢谢。 这是正确的方法。使用 Semaphores 和 FutureTasks 是我们想做的更具代表性的范例。更改后代码大大简化,这始终是一个好兆头。此外,它还解决了任何 InheritableThreadLocal 问题。【参考方案2】:

为什么不直接将当前连接传递给主任务产生的任何子任务?也许是某种共享的上下文对象?

【讨论】:

【参考方案3】:

使用InheritedThreadLocal 几乎肯定是错误的。如果你能适应那个奇怪的工具,你可能没有问过这个问题。 首先,它非常容易泄漏,并且值通常会在一些完全奇怪的线程中逃逸。

至于 Runnable 与上下文相关联。 覆盖ExecutorPool 的publicvoid execute(Runnable command) 并将Runnable 包装在一些上下文中,该上下文首先从InheritedThreadLocal 中携带您想要的值。

包装类应该看起来像

class WrappedRunnable extends Runnable
  static final ThreadLocal<Ctx> context=new ThreadLocal<Ctx>();
  final Runnable target;
  final Ctx context;
  WrappedRunnable(Ctx context, Runnable target)...

  public void run()
    ctx.set(context);
    try 
      target.run();
    finally
      ctx.set(null);//or ctx.remove()
    
  

或者,是否有任何 ThreadPoolExecutor 的实现在每次启动新的 Runnable 时创建一个新的 >thread?出于我的目的,我只关心将>同时运行的线程数设置为固定大小。

虽然从性能的角度来看确实很糟糕,但您可以实现自己的,基本上您只需要为 Executor 生成新线程并启动它的 execute(Runnable task) 方法。

【讨论】:

是什么让你说 InheritableThreadLocal 容易泄漏?我正在谷歌上搜索它,但没有看到任何文章或示例说明它导致内存泄漏的位置。你能指出一些来源吗? @JeffGoldberg,嗯,我非常清楚它是如何实现的,并且我做了很多基础设施/中间件。 ThreadLocal 容易泄漏,继承的版本非常容易泄漏。实际上,无论出于何种原因,您都无法控制何时产生新线程(JDK 本身就是这样做的)。长时间运行的线程会泄漏值及其类加载器,并且无法阻止那个遥远的线程持有对其他无用对象的引用。 childValue 可以改正,但在持有它的线程中调用,新的目标线程未知... 快速说明:这种方法确实有效。我最终接受了@forty-2 的建议并改变了方法,否则我会选择这个作为答案。感谢您的建议和帮助。【参考方案4】:

我们之前遇到过同样的问题,我们通过编写 ThreadLocalContextMigrator 解决了这个问题,它基本上将线程本地上下文复制到将使用池中的线程执行的任务。任务在执行时将收集更多上下文信息,并在任务完成后将其复制回来。

【讨论】:

你能提供更多关于你是如何做到这一点的细节吗? ThreadLocalContextMigrator.java 是否公开可用?

以上是关于将 InheritableThreadLocal 与 ThreadPoolExecutor - 或 - 不重用线程的 ThreadPoolExecutor 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程-ThreadLocal和InheritableThreadLocal的使用

InheritableThreadLocal-实现线程共享数据

InheritableThreadLocal原理

InheritableThreadLocal详解

多线程-InheritableThreadLocal

InheritableThreadLocal原理解析