blog5开发:session设置属性的线程安全问题

本来有这样一段代码:

public static void addKey(HttpServletRequest request, LockKey key, LockResource lockResource) {
    Map<String, List<LockKey>> keysMap = (Map<String, List<LockKey>>) getKeysMap(request);
    if (keysMap == null) {
        keysMap = new HashMap<>();
    }
    List<LockKey> keys = keysMap.get(lockResource.getResourceId());
    if (CollectionUtils.isEmpty(keys)) {
        keys = new ArrayList<>();
        keys.add(key);
        keysMap.put(lockResource.getResourceId(), keys);
    } else {
        keys.removeIf(_key -> _key.lockId().equals(key.lockId()));
        keys.add(key);
    }
    request.getSession().setAttribute(LOCKKEY_SESSION_KEY, keysMap);
}

当初也没注意,现在才发现它不是线程安全的,如果两个线程同时访问的话,其中某个LockKey就会丢失了,用Spring的话,它提供了一个WebUtils#getSessionMutex(HttpSession session)方法,用来获取一个同步对象,看了它源码就会发现它首先会从session中取出WebUtils.class.getName() + ".MUTEX"这个属性,如果不存在,返回session本身,但同时注释中提到:

In many cases, the HttpSession reference itself is a safe mutex as well, since it will always be the same object reference for the same active logical session. However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.

因此,最好的方法还是要在web.xml中加一个session listener:

<listener>
  <listener-class>
     org.springframework.web.util.HttpSessionMutexListener
  </listener-class>
</listener>

最终方法如下:

HttpSession session = request.getSession();
synchronized (WebUtils.getSessionMutex(session)) {
  Map<String, List<LockKey>> keysMap = (Map<String, List<LockKey>>) getKeysMap(request);
  if (keysMap == null) {
      keysMap = new HashMap<>();
  }
  List<LockKey> keys = keysMap.get(lockResource.getResourceId());
  if (CollectionUtils.isEmpty(keys)) {
      keys = new ArrayList<>();
      keys.add(key);
      keysMap.put(lockResource.getResourceId(), keys);
  } else {
      keys.removeIf(_key -> _key.lockId().equals(key.lockId()));
      keys.add(key);
  }
  session.setAttribute(LOCKKEY_SESSION_KEY, keysMap);
}