4

我们在基于 Spring 的 Web 应用程序中有一个服务实现,它增加了数据库中的一些统计计数器。因为我们不想弄乱用户的响应时间,所以我们使用 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"在这里设置以避免这些冲突是个好主意吗?这有什么副作用吗?我们有很多地方会触发异步操作来更新统计信息。

4

2 回答 2

9

与单个线程处理它们的速度相比,副作用将取决于将任务添加到执行程序的速度。如果每秒添加的任务数量大于单个线程在一秒钟内可以处理的数量,则队列大小会随着时间的推移而增加,直到最终出现内存不足错误。

查看此页面Task Execution的executor部分。他们表示拥有无界队列不是一个好主意。

如果您知道您可以比添加任务更快地处理任务,那么您可能是安全的。如果没有,您应该添加队列容量并在队列达到此大小时处理输入线程阻塞。

于 2013-06-24T15:11:02.937 回答
4

查看您发布的两个示例,而不是 @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);
            // ...
        }
    }   
}
于 2013-06-24T20:59:45.370 回答