使用request.getSession()作为锁定对象?

我有一些获取和设置会话属性的java代码:

Object obj = session.getAttribute(TEST_ATTR); if (obj==null) { obj = new MyObject(); session.setAttribute(obj); } 

为了使这段代码成为线程安全的,我想将它包装在一个synchronized块中。 但是我用什么作为锁定对象呢? 使用会话是否有意义?

 synchronized (session) { Object obj = session.getAttribute(TEST_ATTR); if (obj==null) { obj = new MyObject(); session.setAttribute(obj); } } 

通常不赞成使用你无法控制的锁。 锁定应尽可能紧密,因为会话或多或少是一个全局对象,它不适合账单。 尝试使用java.util.concurrent.locks包中的单独锁,并将其作用于您的类。

在servlet的上下文中? Servlet可以分布在多个进程中,因此您不能总是拥有相同的会话对象。 这样做的一个推论是,servlet容器可能决定在同一进程中为您提供不同的会话对象。

IIRC,Brian Goetz撰写了一篇有趣的文章,讲述了通过会话做事的难度。

我的建议:尽可能远离会话,不要锁定随机对象(使用没有其他目的的锁对象)。

我看了你发布的文章。 您可以跳过同步所有内容并采用与作者相同的方法,使用比较和设置来确保您的数据正确:

 ServletContext ctx = getServletConfig().getServletContext(); AtomicReference holder = (AtomicReference) ctx.getAttribute(TEST_ATTR); while (true) { TYPE oldVal = holder.get(); TYPE newVal = computeNewVal(oldVal); if (holder.compareAndSet(oldVal, newVal)) break; } 

如果某个其他线程自上次读取后更新了“holder”的值,holder.compareAndSet(old,new)将返回false。 holder.compareAndSet(,)放在while(true)循环中,这样如果值在您能够写入之前确实发生了变化,那么您将有机会再次读取该值并重新尝试写入。

http://java.sun.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicReference.html

该规范并不保证这将有所帮助:

 synchronized (session) { Object obj = session.getAttribute(TEST_ATTR); if (obj==null) { obj = new MyObject(); session.setAttribute(obj); } } 

(它可能适用于特定的实现,但不保证它将在所有容器中工作。)

Servlet 2.5 MR6说:

执行请求线程的多个servlet可以同时具有对同一会话对象的活动访问权。 容器必须确保以线程安全的方式执行表示会话属性的内部数据结构的操作。 开发人员负责线程安全访问属性对象本身。 这将保护HttpSession对象内的属性集合免于并发访问,从而消除了应用程序导致该集合损坏的机会。

基本上,规范使它成为你的问题。 您的解决方案必须根据您的应用程序设计和部署计划进行定制。 我不确定这个问题的全球解决方案是否适用于所有情况; 但是,如果您更具体地了解应用程序的体系结构和应用程序服务器的配置,则可能会得到更好的答案。

您的代码至少有两个原因无效。

1)如果会话不存在,那么您可以轻松地为同一个用户创建两次,并且具有丑陋的竞争条件。

2)如果会话不是跨线程的同一个对象,那么无论如何它都不会工作。 会话可能是equals()到另一个线程中的同一个会话,但这不起作用。

您不需要锁定,因为session.setAttribute()是线程安全的(请参阅上面的@McDowell的servlet规范注释)。

但是,让我们使用一个不同的例子。 假设你想要削减属性的值,然后在<= 100时更新它。在这种情况下,你需要同步化getAttribute()的代码块compare = 100和setAttribute()

现在,你应该用什么锁? 请记住,如果锁定使用不同的对象,则不会进行同步。 因此,不同的代码块必须使用相同的对象。 您选择的session对象可能没问题。 还要记住,即使您已经锁定,不同的代码块也可以访问会话(读/写),除非其他代码也锁定会话对象。 这里的一个陷阱是代码中的太多地方会锁定会话对象,因此必须等待。 例如,如果您的代码块使用会话属性A而另一个代码块使用会话属性B,那么如果他们不需要通过同时锁定会话对象而彼此等待就会很好。 使用名为LockForA和LockForB的静态对象可能是您使用代码的更好选择 – 例如synchronized (LockForA) { }.

我遇到了同样的问题,并且不希望将它扩展到我的类,因为我的对象的创建可能需要一秒钟,并且已经创建了对象的其他会话的停顿线程。 我不想使用请求的会话对象进行同步,因为应用程序服务器实现可能会在不同的请求中返回不同的会话Facade,并且在不同的对象上进行同步根本不起作用。 所以我选择在会话上放置一个对象作为锁使用并使用双重检查惯用法来确保只创建一次锁。 在MyObject范围内进行同步不再是问题,因为创建对象非常快。

 Object lock = session.getAttribute("SessionLock"); if (lock == null) { synchronized (MyObject.class) { lock = session.getAttribute("SessionLock"); if(lock == null) { lock = new Object(); session.setAttribute("SessionLock", lock); } } } synchronized (lock) { Object obj = session.getAttribute(TEST_ATTR); if (obj==null) { obj = new MyObject(); session.setAttribute(obj); } }