有关在Spring单例作用域服务中使用ThreadLocal的问题

时间:2016-05-19 19:15:42

标签: java spring multithreading concurrency thread-local

在下面的单例作用域服务类中,类中的所有方法都需要一些在调用eventprofiler时已知的用户上下文。我考虑将这些值存储在Service.doA()中,而不是在方法之间传递信息。关于这种方法我有两个问题:

1)下面的实现是否正确使用TheadLocal?也就是说,它是线程安全的,并且正确的值将被读/写到ThreadLocal

2)是否需要明确清除ThreadLocal以防止任何内存泄漏?它会被垃圾收集吗?

ThreadLocal userInfo

3 个答案:

答案 0 :(得分:7)

1)示例代码是正常的,除了doB和doC中的名称冲突,你使用与引用ThreadLocal的静态变量相同的名称,就像保存从ThreadLocal中提取的内容的本地变量一样。

2)存储在ThreadLocal中的对象保持附加到该线程,直到被明确删除。例如,如果您的服务在servlet容器中执行,则当请求完成时,其线程将返回池中。如果你还没有清理线程的ThreadLocal变量内容,那么该数据将随之附带,以配合线程为下一个分配的任何请求。每个线程都是GC根,附加到线程的threadlocal变量在线程死亡之前不会被垃圾收集。根据{{​​3}}:

  

每个线程都拥有对其线程局部变量副本的隐式引用,只要该线程处于活动状态并且可以访问ThreadLocal实例;一个线程消失后,它的所有线程局部实例副本都会被垃圾收集(除非存在对这些副本的其他引用)。

如果您的上下文信息仅限于一个服务的范围,那么最好通过参数而不是使用ThreadLocal传递信息。 ThreadLocal适用于需要跨不同服务或不同层提供信息的情况,如果只有一个服务使用它,您似乎只会过度复杂化代码。现在,如果您有AOP建议在不同的不同对象上使用的数据,那么将这些数据放在threadlocal中可能是一种有效的用法。

通常,您可以通过当前处理来识别线程的执行点,例如在servlet过滤器中,可以在线程返回到线程池之前删除threadlocal变量。你不会使用try-finally块,因为你插入threadlocal对象的地方远离你正在清理它的地方。

答案 1 :(得分:2)

当你使用ThreadLocal时,你需要确保你清理它,因为:

  1. 它会以某种方式创建内存泄漏,因为GC无法收集该值,因为当且仅当没有对象直接或间接具有对象的硬引用时,该对象才有资格使用GC。例如,在这里,您的ThreadLocal实例间接地通过其内部ThreadLocalMap对您的值进行了硬性引用,摆脱此硬引用的唯一方法是调用ThreadLocalMap#remove(),因为它将从ThreadLocalMap中删除值。如果您的ThreadLocal实例本身符合GC的条件,那么另一种使您的价值符合GC条件的潜在方式就是Service类中的常量,因此它永远不会符合条件对于GC来说,这就是我们想要的。因此,唯一的预期方式是致电ThreadLocalMap#remove()
  2. 它创建的bug很难找到,因为大多数时候使用ThreadLocal的线程都是thread pool的一部分,这样线程就会被重用于另一个请求,所以如果你的{{1}如果没有正确清理,线程将重用存储在ThreadLocal中的对象实例,该实例甚至可能与导致复杂错误的新请求无关。所以这里举例来说,我们可以仅仅因为ThreadLocal没有被清理而得到不同用户的结果。
  3. 所以模式如下:

    ThreadLocal

    关于线程安全,即使try { userInfo.set(new UserInfo(userId, name)); // Some code here } finally { // Clean up your thread local whatever happens userInfo.remove(); } 不是线程安全的,它当然是线程安全的,因为每个线程都将使用自己的UserInfo实例,因此没有UserInfo的实例存储到多个线程将访问或修改UserInfo,因为ThreadLocal值以某种方式限定当前线程。

答案 2 :(得分:0)

使用后一定要清理干净。 ThreadLocals非常容易泄漏内存,堆内存和permgen / metaspace内存通过类别泄漏。在你的情况下,最好的方法是:

public void doA() {
  // finds user info
  userInfo.set(new UserInfo(userId, name));
  try {
    doB();
    doC();
  } finally {
    userInfo.remove()
  }
}