关于在 Spring 单例范围服务中使用 ThreadLocal 的问题
Questions about using ThreadLocal in a Spring singleton scoped service
在我下面的单例范围服务 class 中,class 中的所有方法都需要一些在调用 Service.doA()
时已知的用户上下文。我没有跨方法传递信息,而是考虑将这些值存储在 TheadLocal
中。我对这种方法有两个问题:
1) 下面的实现是否正确使用了 ThreadLocal
?也就是说,它是线程安全的,正确的值将是 read/written 到 ThreadLocal
?
2) ThreadLocal userInfo
是否需要明确清理以防止任何内存泄漏?它会被垃圾收集吗?
@Service
public class Service {
private static final ThreadLocal<UserInfo> userInfo = new ThreadLocal<>();
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
doB();
doC();
}
private void doB() {
// needs user info
UserInfo userInfo = userInfo.get();
}
private void doC() {
// needs user info
UserInfo userInfo = userInfo.get();
}
}
用完一定要清理干净。 ThreadLocals 非常容易泄漏内存,堆内存和 permgen/metaspace 内存通过 classloders 泄漏。在您的情况下,最好的方法是:
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
try {
doB();
doC();
} finally {
userInfo.remove()
}
}
1) 示例代码没问题,除了 doB 和 doC 中的名称冲突,其中您对引用 ThreadLocal 的静态变量使用相同的名称,就像您对保存从ThreadLocal.
2) 您存储在 ThreadLocal 中的对象会一直附加到该线程,直到被显式删除。如果您的服务在 servlet 容器中执行,例如,当请求完成时,它的线程 returns 进入池。如果您还没有清理线程的 ThreadLocal 变量内容,那么该数据将一直存在,以伴随线程为下一个分配的任何请求。每个线程都是一个 GC 根,附加到线程的线程局部变量在线程死后才会被垃圾回收。根据the API doc:
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
如果您的上下文信息仅限于一项服务的范围,您最好通过参数传递信息而不是使用 ThreadLocal。 ThreadLocal 适用于需要在不同服务或不同层中提供信息的情况,如果仅由一项服务使用,您的代码似乎只会过于复杂。现在,如果您有 AOP 建议在不同的不同对象上使用的数据,则将该数据放在线程本地中可能是一种有效的用法。
要执行清理,通常您会确定线程完成当前处理的点,例如在 servlet 过滤器中,线程局部变量可以在线程返回到线程池之前被删除。您不会使用 try-finally 块,因为插入 threadlocal 对象的地方离您清理它的地方很远。
当您使用 ThreadLocal
时,无论发生什么,您都需要确保清理它,因为:
- 它会以某种方式造成内存泄漏,因为 GC 无法收集该值,因为当且仅当不再有对象直接或间接硬引用该对象时,该对象才符合 GC 的条件。因此,例如,您的
ThreadLocal
实例通过其内部 ThreadLocalMap
间接地硬引用了您的值,摆脱这种硬引用的唯一方法是调用 ThreadLocalMap#remove()
从 ThreadLocalMap
中删除值。使您的值符合 GC 条件的另一种可能方法是,您的 ThreadLocal
实例本身符合 GC 条件,但这里它在 class Service
中是一个常量,所以它永远不会有资格获得我们想要的 GC。所以唯一预期的方法是调用 ThreadLocalMap#remove()
.
- 它会产生很难发现的错误,因为大多数时候使用您的
ThreadLocal
的线程是 thread pool
的一部分,这样线程将被重新用于另一个请求,所以如果您的 ThreadLocal
没有被正确清理,线程将重用存储在 ThreadLocal
中的对象实例,它甚至可能与导致复杂错误的新请求无关。例如,这里我们可以得到不同用户的结果,只是因为 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
将被多个线程访问或修改,因为 ThreadLocal
值以某种方式限定了当前线程的范围。
在我下面的单例范围服务 class 中,class 中的所有方法都需要一些在调用 Service.doA()
时已知的用户上下文。我没有跨方法传递信息,而是考虑将这些值存储在 TheadLocal
中。我对这种方法有两个问题:
1) 下面的实现是否正确使用了 ThreadLocal
?也就是说,它是线程安全的,正确的值将是 read/written 到 ThreadLocal
?
2) ThreadLocal userInfo
是否需要明确清理以防止任何内存泄漏?它会被垃圾收集吗?
@Service
public class Service {
private static final ThreadLocal<UserInfo> userInfo = new ThreadLocal<>();
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
doB();
doC();
}
private void doB() {
// needs user info
UserInfo userInfo = userInfo.get();
}
private void doC() {
// needs user info
UserInfo userInfo = userInfo.get();
}
}
用完一定要清理干净。 ThreadLocals 非常容易泄漏内存,堆内存和 permgen/metaspace 内存通过 classloders 泄漏。在您的情况下,最好的方法是:
public void doA() {
// finds user info
userInfo.set(new UserInfo(userId, name));
try {
doB();
doC();
} finally {
userInfo.remove()
}
}
1) 示例代码没问题,除了 doB 和 doC 中的名称冲突,其中您对引用 ThreadLocal 的静态变量使用相同的名称,就像您对保存从ThreadLocal.
2) 您存储在 ThreadLocal 中的对象会一直附加到该线程,直到被显式删除。如果您的服务在 servlet 容器中执行,例如,当请求完成时,它的线程 returns 进入池。如果您还没有清理线程的 ThreadLocal 变量内容,那么该数据将一直存在,以伴随线程为下一个分配的任何请求。每个线程都是一个 GC 根,附加到线程的线程局部变量在线程死后才会被垃圾回收。根据the API doc:
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
如果您的上下文信息仅限于一项服务的范围,您最好通过参数传递信息而不是使用 ThreadLocal。 ThreadLocal 适用于需要在不同服务或不同层中提供信息的情况,如果仅由一项服务使用,您的代码似乎只会过于复杂。现在,如果您有 AOP 建议在不同的不同对象上使用的数据,则将该数据放在线程本地中可能是一种有效的用法。
要执行清理,通常您会确定线程完成当前处理的点,例如在 servlet 过滤器中,线程局部变量可以在线程返回到线程池之前被删除。您不会使用 try-finally 块,因为插入 threadlocal 对象的地方离您清理它的地方很远。
当您使用 ThreadLocal
时,无论发生什么,您都需要确保清理它,因为:
- 它会以某种方式造成内存泄漏,因为 GC 无法收集该值,因为当且仅当不再有对象直接或间接硬引用该对象时,该对象才符合 GC 的条件。因此,例如,您的
ThreadLocal
实例通过其内部ThreadLocalMap
间接地硬引用了您的值,摆脱这种硬引用的唯一方法是调用ThreadLocalMap#remove()
从ThreadLocalMap
中删除值。使您的值符合 GC 条件的另一种可能方法是,您的ThreadLocal
实例本身符合 GC 条件,但这里它在 classService
中是一个常量,所以它永远不会有资格获得我们想要的 GC。所以唯一预期的方法是调用ThreadLocalMap#remove()
. - 它会产生很难发现的错误,因为大多数时候使用您的
ThreadLocal
的线程是thread pool
的一部分,这样线程将被重新用于另一个请求,所以如果您的ThreadLocal
没有被正确清理,线程将重用存储在ThreadLocal
中的对象实例,它甚至可能与导致复杂错误的新请求无关。例如,这里我们可以得到不同用户的结果,只是因为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
将被多个线程访问或修改,因为 ThreadLocal
值以某种方式限定了当前线程的范围。