如何在不使用InheritableThreadLocal的情况下为每个顶级进程/线程创建共享上下文?

我想看看是否有一个很好的模式,可以在不使用InheritableThreadLocal的情况下在顶级线程的所有类和子线程之间共享上下文。

我有几个顶级进程,每个进程都在自己的线程中运行。 这些顶级进程通常会产生临时子线程。

我希望每个顶级流程都拥有并管理自己的数据库连接。

我不想传递从类到类以及从线程到子线程的数据库连接(我的同事称之为“社区自行车”模式)。 这些是顶级的大流程,这意味着可能会编辑数百个方法签名来传递这个数据库连接。

现在我调用单例来获取数据库连接管理器。 单例使用InheritableThreadLocal以便每个顶级进程都有自己的版本。 虽然我知道有些人遇到单身人士问题,但这意味着只要我需要正确管理的连接,我就可以说DBConnector.getDBConnection(args) (用语)。 如果我能找到一个更好但仍然干净的解决方案,我并不依赖于这种方法。

由于各种原因, InheritableThreadLocal被certificate是棘手的。 (见这个问题 。)

有没有人有一个建议来处理这种不需要InheritableThreadLocal或在整个地方传递一些上下文对象的东西?

谢谢你的帮助!


更新:我设法解决了眼前的问题(请参阅相关问题),但我仍然希望了解其他可能的方法。 下面的四十二个建议是好的并且确实有效(谢谢!),但请参阅评论为什么它有问题。 如果人们投票支持jtahlborn的答案,并告诉我,我因为想要避免绕过我的数据库连接而迷恋,那么我会松懈,选择那个作为我的答案,并修改我的世界观。

我没有对此进行过测试,但我的想法是创建一个自定义的ThreadPoolExecutor,它知道如何获取上下文对象并使用#beforeExecute()将上下文对象传递给将要执行任务的线程。 要成为一个好公民,你还应该清除#afterEXecute()中的上下文对象,但我把它留作练习。

 public class XyzThreadPoolExecutor extends ThreadPoolExecutor { public XyzThreadPoolExecutor() { super(3, 3, 100, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new MyThreadFactory()); } @Override public void execute(Runnable command) { /* * get the context object from the calling thread */ Object context = null; super.execute(new MyRunnable(context, command)); } @Override protected void beforeExecute(Thread t, Runnable r) { ((MyRunnable)r).updateThreadLocal((MyThread) t); super.beforeExecute(t, r); } private static class MyThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { return new MyThread(r); } } private class MyRunnable implements Runnable { private final Object context; private final Runnable delegate; public MyRunnable(Object context, Runnable delegate) { super(); this.context = context; this.delegate = delegate; } void updateThreadLocal(MyThread thread) { thread.setContext(context); } @Override public void run() { delegate.run(); } } private static class MyThread extends Thread { public MyThread(Runnable target) { super(target); } public void setContext(Object context) { // set the context object here using thread local } } } 

“社区自行车”解决方案(正如您所说)实际上比您当前使用的全局(或伪全局)单例要好得多。 它使代码可测试,并且可以很容易地选择哪些类使用哪个上下文。 如果做得好, 不需要将上下文对象添加到每个方法签名。 您通常会确保所有“主要”类都引用当前上下文,并且任何“次要”类都可以访问相关的“主要”类。 可能需要访问上下文的一次性方法需要更新其方法签名,但大多数类应该通过成员变量提供上下文。

由于ThreadLocal本质上是一个键入你的线程的Map ,你不能实现一个键入你的线程名称Map吗? 您需要的只是一种符合您要求的有效命名策略。

作为一个Lisper,我非常同意你的世界观,如果你要修改它,会认为这是一种耻辱。 🙂

如果是我,我只需为每个顶级进程使用ThreadGroup ,并将每个连接与调用者运行的组相关联。如果与线程池一起使用,只需确保池使用正确线程组中的线程(例如,通过为每个线程组创建一个池)。

示例实现:

 public class CachedConnection { /* Whatever */ } public class ProcessContext extends ThreadGroup { private static final Map> contexts = new WeakHashMap>(); public static T getContext(Class cls) { ProcessContext tg = currentContext(); Map ctx; synchronized(contexts) { if((ctx = contexts.get(tg)) == null) contexts.put(tg, ctx = new HashMap()); } synchronized(ctx) { Object cur = ctx.get(cls); if(cur != null) return(cls.cast(cur)); T new_t; try { new_t = cls.newInstance(); } catch(Exception e) { throw(new RuntimeException(e)); } ctx.put(cls, new_t); return(new_t); } } public static ProcessContext currentContext() { ThreadGroup tg = Thread.currentThread().getThreadGroup(); while(true) { if(tg instanceof ProcessContext) return((ProcessContext)tg); tg = tg.getParent(); if(tg == null) throw(new IllegalStateException("Not running in a ProcessContext")); } } } 

如果您只是确保在适当的ProcessContext运行所有线程,则可以通过调用ProcessContext.getContext(CachedConnection.class)在任何地方获取ProcessContext.getContext(CachedConnection.class)

当然,如上所述,您必须确保您委托的任何其他线程也能在正确的ProcessContext中运行,但我很确定您的描述中存在问题 – 您显然需要指定不知怎的 ,你的代表团工作人员遇到了多个上下文中的哪一个。如果有的话,可以想象修改ProcessContext如下:

 public class ProcessContext extends ThreadGroup { /* getContext() as above */ private static final ThreadLocal tempctx = new ThreadLocal(); public static ProcessContext currentContext() { if(tempctx.get() != null) return(tempctx.get()); ThreadGroup tg = Thread.currentThread().getThreadGroup(); while(true) { if(tg instanceof ProcessContext) return((ProcessContext)tg); tg = tg.getParent(); if(tg == null) throw(new IllegalStateException("Not running in a ProcessContext")); } } public class RunnableInContext implements Runnable { private final Runnable delegate; public RunnableInContext(Runnable delegate) {this.delegate = delegate;} public void run() { ProcessContext old = tempctx.get(); tempctx.set(ProcessContext.this); try { delegate.run(); } finally { tempctx.set(old); } } } public static Runnable wrapInContext(Runnable delegate) { return(currentContext().new RunnableInContext(delegate)); } } 

这样,您可以使用ProcessContext.wrapInContext()来传递Runnable ,该Runnable在运行时会从创建它的位置inheritance其上下文。

(请注意,我实际上没有尝试过上面的代码,所以它可能充满了拼写错误。)

我不支持你的世界观和jthalborn关于伯爵的想法,即使它更可测试。

虽然从你的问题陈述中首先解释我所理解的是这样的。

  1. 有3或4个顶级进程(它们基本上都有自己的线程)。 连接对象就是diffrenet。
  2. 您需要设置和完成一次Connection的一些基本特性。
  3. 子线程决不会将顶层线程中的Connection对象passe更改为它们。

这是我的建议,你确实需要一个时间,你需要设置连接,但是在你的每个顶层进程中,你做1)进一步处理该连接2)保持一个InheriatbleThreadLocal(和子进程)您的顶级线程将具有已修改的连接对象.3)传递这些threasd实现类。 执行者中的MyThread1,MyThread2,MyThread3,… MyThread4。 (这与你的另一个相关问题不同,如果你需要一些门控,信号量是一种更好的方法)

为什么我说它不比jthalborn的观点更可测试的是,在这种情况下,你无论如何也需要提供模拟的Connection对象。 这里也。 另外,直接传递对象并将对象保留在ThreadLocal中是一样的(InheritableThreadLocal是一个以java内置方式传递的地图,我相信这里没什么不好的)。

编辑:我确实记得它是一个封闭的系统,我们没有“免费”线程临时连接