Java ServletContext对象的线程安全

Java ServletContext对象的线程安全,java,jakarta-ee,servlets,Java,Jakarta Ee,Servlets,我正在ServletContext中存储一个HashMap对象。 但是多个请求线程正在读取和修改这个HashMap 因为我相信ServletContext对象是在请求线程之间共享的,所以我需要同步对此HashMap的访问吗?或者还有其他更好的方法来实现这一点吗?在多线程环境中,最好的方法是使用java.util.concurrent.ConcurrentHashMap。 它是专门设计的,允许您阅读和修改它,而无需任何ConcurrentModificationException。然而,在迭代的情

我正在ServletContext中存储一个HashMap对象。 但是多个请求线程正在读取和修改这个HashMap


因为我相信ServletContext对象是在请求线程之间共享的,所以我需要同步对此HashMap的访问吗?或者还有其他更好的方法来实现这一点吗?

在多线程环境中,最好的方法是使用java.util.concurrent.ConcurrentHashMap。 它是专门设计的,允许您阅读和修改它,而无需任何
ConcurrentModificationException
。然而,在迭代的情况下,您应该对其实例进行同步,以便始终获得可预测的结果

如果您以多线程方式从上下文中检索其他内容,那么在上下文上进行同步会给您带来大量开销。因此,
ConcurrentHashMap
是一个更好的解决方案

您可以在此处阅读:

一种支持完全并发的检索和调整的哈希表 更新的预期并发性。检索操作(包括get) 通常不阻塞,因此可能与更新操作重叠 (包括放置和移除)


您需要同步ServletContext对象。例如:-

synchronized(ctx){
 //your code here
}

正如
@Artem Moskalev
所建议的,您可以使用ConcurrentHashMap和
putIfAbsent
方法来存储对象/值,而不是简单的
put
方法


我想在
@Artem Moskalev'
的回答下面添加这条评论,但我没有足够的声誉

是的,请确保在修改
HashMap
时;它是同步的或线程安全的

通过
ServletContext#setAttribute
发布属性是线程安全的!这可以从JavaServlet规范第4.5章派生:(…)任何属性绑定 在同一个Web中的任何其他servlet都可以访问上下文 应用程序。(……)

(原因:使对象对其他servlet可用意味着也使它们对其他线程可用。这只有在使用了正确的同步时才可能,因此对于实现
ServletContext\setAttribute
的所有servlet容器,同步都是必需的)

因此,通过
ServletContext#getAttribute
读取已发布属性也是如此

当然,如果像HashMap这样的对象在不同线程之间共享,那么开发人员必须确保以适当的线程安全方式访问该共享对象本身!如您问题的其他答案中所述,使用
ConcurrentHashMap
是一种可能的解决方案,但在初始化属性时不会解决竞争条件,因为
null
检查将不是原子的:

ConcurrentMap<String, Object> shared = (...)servletContext.getAttribute("sharedData");
if (shared == null) {
    shared = new ConcurrentHashMap<>();
    servletContext.setAttribute("sharedData", shared);
}
因此,每个servlet上下文属性值必须

  • 不可变(通过最终字段)或有效不可变,或
  • 可变,但在共享后不会发生突变,或
  • 以线程安全的方式实现(例如同步)

(这适用于Java中线程之间共享的所有类型的对象,不仅仅是关于servlet上下文属性)

有一些代表。您可以立即添加注释。:)作为一般规则,您不应该使用您不控制的对象的固有(监视器)锁。我只是对这些术语有点困惑。一方面,您说在ServletContext中设置属性是线程安全的,另一方面,必须使用正确的同步。我认为,同步正是为了保护线程不安全的上下文属性。如果我错了,请纠正我。@escudero380我的意思是,Java Servlet规范的每个实现都必须在
setAttribute
中使用适当的同步,否则它将无法满足第4.5章中引用的要求。@escudero380我稍微更改了答案,并添加了另一个澄清部分来解决此问题。感谢您的澄清。无论如何,据我所知,上下文范围的属性主要用于存储在加载应用程序时设置的只读值,但不一定是共享请求时生成的值的好地方。。。而且在分布式环境中,整个集群似乎无法访问这些属性,因此从扩展项目的角度来看,应该使用一些外部资源(如数据库)作为共享应用程序全局信息的位置-level@escudero380我完全同意。
// servlet 1:
Person p = new Person("Keith", "Richards");
context.setAttribute('key', p); // share p
p.setName("Ron", "Wood"); // modification AFTER sharing

// servlet 2 (some time LATER):
Person p = context.getAttribute();
// now, p is guaranteed to be non-null,
// but if class Person is not thread-safe by itself, it may be any of
// - "Keith Richards"
// - "Keith Wood"
// - "Ron Richards"
// - "Ron Wood"
// (depending on the implementation of setName, it may be even worse!)