我们在基于Spring的Web应用程序中有一个服务实现,它增加了db中的一些统计计数器。由于我们不想弄乱用户的响应时间,因此我们使用Spring的@Async异步定义它们:
public interface ReportingService {
@Async
Future<Void> incrementLoginCounter(Long userid);
@Async
Future<Void> incrementReadCounter(Long userid, Long productId);
}
这样的spring任务配置:
<task:annotation-driven executor="taskExecutor" />
<task:executor id="taskExecutor" pool-size="10" />
现在,拥有pool-size="10"
,当两个线程尝试两个线程创建包含计数器的相同初始记录时,我们会遇到并发问题。
这里设置pool-size="1"
以避免这些冲突是一个好主意吗?这有副作用吗?我们有很多地方可以触发异步操作来更新统计信息。
答案 0 :(得分:7)
与单个线程处理它们的速度相比,副作用将取决于将任务添加到执行程序的速度。如果每秒添加的任务数大于单个线程在一秒钟内可以处理的数量,那么就会冒着队列随时间增加的风险,直到最终出现内存不足错误。
在此页Task Execution查看执行者部分。他们声称拥有一个无限制的队列并不是一个好主意。
如果您知道可以比添加任务更快地处理任务,那么您可能很安全。如果没有,您应该添加队列容量并在队列达到此大小时处理输入线程阻塞。
答案 1 :(得分:3)
查看您发布的两个示例,而不是@Async调用的常量流,考虑在客户端请求时更新JVM本地变量,然后让后台线程不时地将其写入数据库。沿着(介意半伪代码):
class DefaultReportingService implements ReportingService {
ConcurrentMap<Long, AtomicLong> numLogins;
public void incrementLoginCounterForUser(Long userId) {
numLogins.get(userId).incrementAndGet();
}
@Scheduled(..)
void saveLoginCountersToDb() {
for (Map.Entry<Long, AtomicLong> entry : numLogins.entrySet()) {
AtomicLong counter = entry.getValue();
Long toBeSummedWithTheValueInDb = counter.getAndSet(0L);
// ...
}
}
}