【问题标题】:Using request.getSession() as a locking object?使用 request.getSession() 作为锁定对象?
【发布时间】:2009-11-30 15:50:58
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: java session concurrency locking thread-safety


    【解决方案1】:

    通常不赞成使用您无法控制的锁。锁的范围应该尽可能地紧凑,并且由于会话或多或少是一个全局对象,因此它不符合要求。尝试使用 java.util.concurrent.locks 包中的单独锁并将其范围限定为您的类。

    【讨论】:

    • 这不是双重检查锁定,这只是将一个值放入映射中,如果它不存在。
    • 将锁定设置为我的类(不是 servlet,而是从 jsp 页面中创建的类)是否会确保所有请求都在同一个锁定上同步?
    • 如果类是在 JSP 页面中创建的,为什么会有任何并发​​问题需要锁定?
    • 我能想到的唯一情况是用户打开了两个窗口(同一个会话)并且两个请求都尝试同时获取和设置会话属性。这真的太牵强,我可以打折吗?
    • 但不是他的会话的 2 个实例,这就是问题所在。将锁定范围限定为您的类并使其成为静态将不允许同时写入多个不同的会话。即,如果您使用静态锁,对一个会话的写入将不得不等待对一个完全不同的会话的写入。我不认为这是你想要的。
    【解决方案2】:

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

    IIRC,Brian Goetz 写了一篇有趣的文章,讨论了通过会话正确处理事情的难度。

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

    【讨论】:

    【解决方案3】:

    我看了你发布的文章。您可以跳过所有同步,并采用与作者相同的方法,通过使用 compare-and-set 来确保您的数据正确:

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

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

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

    【讨论】:

      【解决方案4】:

      规范不保证这会有所帮助:

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

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

      Servlet 2.5 MR6 说:

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

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

      【讨论】:

        【解决方案5】:

        您的代码无法运行至少有两个原因。

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

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

        【讨论】:

          【解决方案6】:

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

          但是,让我们使用一个不同的示例。假设您想检查属性的值,然后在 getAttribute() 的代码块,比较 setAttribute()。

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

          【讨论】:

            【解决方案7】:

            我遇到了同样的问题,不想将其限定在我的班级,因为我的对象的创建可能需要一秒钟,并且会停止已经创建该对象的其他会话的线程。我不想使用请求的会话对象进行同步,因为应用程序服务器实现可能会在不同的请求中返回不同的会话外观,并且在不同的对象上同步根本不起作用。所以我选择在会话上放置一个对象作为锁使用,并使用双重检查习惯来确保锁只创建一次。在 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);
              }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2010-12-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多