【发布时间】:2019-07-23 16:33:51
【问题描述】:
我正在开发一个通过注解声明并发聚合的支持库。
但是我有一个很难解决的问题。
当项目中大量使用 ThreadLocal 时,并发聚合将不起作用,因为 ThreadLocal 的值在多线程中丢失。
举例
public class RequestContext {
private static ThreadLocal<Long> TENANT_ID = new ThreadLocal<>();
public static Long getTenantId() {
return TENANT_ID.get();
}
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static void removeTenantId() {
TENANT_ID.remove();
}
}
@Service
public class HomepageServiceImpl implements HomepageService {
@DataProvider("topMenu")
@Override
public List<Category> topMenu() {
/* will be null */
Long tenantId = RequestContext.getTenantId();
Assert.notNull(tenantId,"tenantId must be not null");
// ... The content hereafter will be omitted.
}
@DataProvider("postList")
@Override
public List<Post> postList() {
/* will be null */
Long tenantId = RequestContext.getTenantId();
Assert.notNull(tenantId,"tenantId must be not null");
// ... The content hereafter will be omitted.
}
@DataProvider("allFollowers")
@Override
public List<User> allFollowers() {
/* will be null */
Long tenantId = RequestContext.getTenantId();
Assert.notNull(tenantId,"tenantId must be not null");
// ... The content hereafter will be omitted.
}
}
并发聚合查询
@Test
public void testThreadLocal() throws Exception {
try {
RequestContext.setTenantId(10000L);
Object result = dataBeanAggregateQueryFacade.get(null,
new Function3<List<Category>, List<Post>, List<User>, Object>() {
@Override
public Object apply(
@DataConsumer("topMenu") List<Category> categories,
@DataConsumer("postList") List<Post> posts,
@DataConsumer("allFollowers") List<User> users) {
return new Object[] {
categories,posts,users
};
}
});
} finally {
RequestContext.removeTenantId();
}
}
以下方法会在不同的线程中被调用。
topMenu()postList()allFollowers()
有什么问题?
问题是上面三个方法都没有得到tenantId。
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateQueryServiceImpl.get(DataBeanAggregateQueryServiceImpl.java:85)
at io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateQueryServiceImpl.get(DataBeanAggregateQueryServiceImpl.java:47)
at io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateQueryServiceImpl.lambda$getDependObjectMap$0(DataBeanAggregateQueryServiceImpl.java:112)
at io.github.lvyahui8.spring.aggregate.service.impl.DataBeanAggregateQueryServiceImpl$$Lambda$197/1921553024.call(Unknown Source)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: tenantId must be not null
at org.springframework.util.Assert.notNull(Assert.java:198)
at io.github.lvyahui8.spring.example.service.impl.HomepageServiceImpl.postList(HomepageServiceImpl.java:36)
... 12 more
我有几个解决方案,我想知道有没有更好的解决方案?
- 按参数传递
- 将
ThreadLocal替换为InheritableThreadLocal - 通过反射复制
ThreadLocal
【问题讨论】:
-
我目前的情况是
ThreadLocal在一个项目中被大量使用,导致并行处理请求比较困难。我无法完全删除ThreadLocal,这需要太多工作。
标签: java spring spring-boot concurrency java.util.concurrent