Servlet 3规范和ThreadLocal

时间:2011-02-20 23:21:41

标签: java multithreading servlets thread-local servlet-3.0

据我所知,Servlet 3规范引入了异步处理功能。除此之外,这意味着可以并且将重用相同的线程来处理另一个并发的HTTP请求。这不是革命性的,至少对于之前与NIO合作的人来说是这样。

无论如何,这导致另一个重要的事情:没有ThreadLocal变量作为请求数据的临时存储。因为如果同一个线程突然成为另一个HTTP请求的运营商线程,请求本地数据将被暴露给另一个请求。

所有这些都是基于阅读文章的纯粹推测,我没有时间玩任何Servlet 3实现(Tomcat 7,GlassFish 3.0.X等)。

所以,问题:

  • 我是否认为ThreadLocal将不再是一个方便的黑客来保存请求数据?
  • 有没有人玩过任何Servlet 3实现并尝试使用ThreadLocal来证明上述内容?
  • 除了在HTTP Session中存储数据外,还有其他类似的易于访问的黑客你可能会建议吗?

编辑:不要误会我的意思。我完全理解危险并且ThreadLocal是一个黑客。事实上,我总是建议不要在类似的环境中使用它。但是,不管你信不信,线程上下文的使用频率比你想象的要频繁得多。一个很好的例子是Spring的OpenSessionInViewFilter,根据它的Javadoc:

  

此过滤器生成Hibernate会话   通过当前线程可用,   将被自动检测   交易经理。

这不是严格ThreadLocal(尚未检查来源)但已经听起来令人担忧。我可以想到更多类似的场景,而且丰富的Web框架使这更有可能。

简单来说,很多人在这个黑客的基础上建造了他们的沙堡,无论是否有意识。因此,斯蒂芬的答案是可以理解的,但并不完全是我所追求的。我想确认是否有人确实尝试能够重现失败的行为,因此这个问题可以用作被同一问题困扰的其他人的参考点

6 个答案:

答案 0 :(得分:9)

异步处理不应该打扰你,除非你明确地要求它。

例如,如果请求的过滤器链中的servlet或任何过滤器未标记为<async-supported>true</async-supported>,则无法使请求异步。因此,您仍然可以使用常规做法进行常规请求。

但是,如果您确实需要异步处理,则需要使用适当的实践。基本上,当异步处理请求时,其处理被分成几部分。这些部分不共享线程本地状态,但是,您仍然可以在每个部分中使用线程本地状态,但您必须在部件之间手动管理状态。

答案 1 :(得分:6)

(警告:我没有详细阅读Servlet 3规范,所以我不能肯定地说该规范说明了你的想法。我只是假设它确实......)

  

我是否正确地假设ThreadLocal将不再是一个方便的黑客来保存请求数据?

使用ThreadLocal始终是一种糟糕的方法,因为当工作线程完成一个请求并从另一个请求开始时,您总是冒着信息泄漏的风险。将东西作为属性存储在ServletRequest对象中总是更好的主意。

现在你只是有另一个理由以“正确”的方式做到这一点。

  

有没有人玩过任何Servlet 3实现并尝试使用ThreadLocals来证明上述内容?

这不是正确的做法。它仅告诉您在测试的特定情况下特定实现的特定行为。你无法概括。

正确的方法是假设有时会发生如果规范说它可以......并设计你的webapp来考虑它。

(不要害怕!显然,在这种情况下,默认情况下不会发生这种情况。您的webapp必须明确启用异步处理功能。如果您的代码充斥着线程本地,则建议您不要这样做。 。)

  

除了在HTTP Session中存储数据外,还有其他类似的易于访问的黑客,你可能会建议。

不。唯一正确的答案是在ServletRequest或ServletResponse对象中存储特定于请求的数据。即使将其存储在HTTP会话中也可能是错误的,因为对于给定的会话,可能有多个请求同时处于活动状态。

答案 2 :(得分:3)

注意:黑客跟随。请谨慎使用,或者实际上不要使用。

只要您继续了解代码执行的线程,就没有理由不能安全地使用ThreadLocal。

try {
    tl.set(value);
    doStuffUsingThreadLocal();
} finally {
    tl.remove();
}

并不是你的调用堆栈是随机切换的。哎呀,如果你想在调用堆栈深处设置ThreadLocal值然后再使用,你也可以破解它:

public class Nasty {

    static ThreadLocal<Set<ThreadLocal<?>>> cleanMe = 
        new ThreadLocal<Set<ThreadLocal<?>>>() {
            protected Set<ThreadLocal<?>> initialValue() {
                return new HashSet<ThreadLocal<?>>();
            }
        };

    static void register(ThreadLocal<?> toClean) {
       cleanMe.get().add(toClean);
    }

    static void cleanup()  {
        for(ThreadLocal<?> tl : toClean)
            tl.remove();
        toClean.clear();
    }
}

然后在设置时注册ThreadLocals,并在某个地方的finally子句中进行清理。这是你不应该做的所有可耻的wankery。对不起,我写了但是为时已晚:/

答案 3 :(得分:1)

我仍然想知道为什么人们使用腐烂的javax.servlet API来实际实现他们的servlet。我做了什么:

  • 我有一个基类HttpRequestHandler,它有一个私有字段用于请求,响应和一个handle()方法,它可以抛出Exception以及一些实用程序方法来获取/设置参数,属性等。我很少需要超过5-10%的servlet API,所以这并不像听起来那么多。

  • 在servlet处理程序中,我创建了这个类的实例,然后忘记了servlet API。

  • 我可以扩展此处理程序类并添加作业所需的所有字段和数据。没有庞大的参数列表,没有线程本地黑客,也不担心并发。

  • 我有一个用于单元测试的实用程序类,它使用模拟的请求和响应实现创建HttpRequestHandler。这样,我不需要servlet环境来测试我的代码。

这解决了我的所有问题,因为我可以在init()方法中获取数据库会话和其他内容,或者我可以在servlet和真正的处理程序之间插入工厂来执行更复杂的操作。

答案 4 :(得分:1)

你是通灵的! (+1为此)

  

我的目标是......获得一个证据,证明它已停止在Servlet 3.0容器中工作

Here就是您要求的证明。

顺便提一下,它使用的是您在问题中提到的完全相同的OEMIV过滤器,猜猜看,它打破了异步servlet处理!

编辑:这是another证明。

答案 5 :(得分:0)

一种解决方案是不使用ThreadLocal,而是使用包含要创建全局对象的静态数组的单例。该对象将包含您设置的“threadName”字段。首先将当前线程的名称(在doGet,doPost中)设置为某个随机唯一值(如UUID),然后将其存储为包含要存储在单例中的数据的对象的一部分。然后,只要代码的某些部分需要访问数据,它就会遍历数组并使用当前正在运行的threadName检查对象并检索对象。当http请求完成时,您需要添加一些清理代码以从数组中删除对象。