让我们再具体说明一下问题:一个感兴趣的应用程序,实现一个 REST 控制器,部署在一个典型的多线程应用程序服务器上(可能还运行其他东西)。问:在处理对控制器映射方法的单独请求时是否存在并发?
我不是这个主题的权威,但它非常重要(特别是:单线程逻辑是否应该应用于 REST-Controller 代码?)。
编辑: 下面的答案是错误的。对同一控制器的不同方法的并发调用同时处理,因此它们使用的所有共享资源(服务、存储库等)必须确保线程安全。然而,出于某种原因,由控制器的相同方法处理的调用被序列化(或者:到目前为止在我看来是这样)。
下面的小测试表明,即使对控制器映射方法的后续(和快速)调用确实由不同的线程处理,单线程逻辑也适用(即没有“开箱即用”的并发性)。
让我们拿控制器:
AtomicInteger count = new AtomicInteger();
@RequestMapping(value = {"/xx/newproduct"})
@ResponseBody
public Answer newProduct(){
Integer atCount = count.incrementAndGet();
////// Any delay/work would do here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Answer ans = new Answer("thread:" + Thread.currentThread().getName() + " controller:" + this, atCount);
count.decrementAndGet();
return ans;
}
并启动 10 个快速(几乎并发 w.r.t. 1000 毫秒睡眠时间)REST 请求,例如通过AngularJS 代码
$scope.newProd = function (cnt) {
var url = $scope.M.dataSource + 'xx/newproduct';
for(var i=0; i<cnt; ++i) {
$http.get(url).success(function(data){
console.log(data);
});
}
};
(Answer 只带有一个String 和一个Integer;使count 成为静态不会改变任何东西)。发生的情况是,所有请求同时变为pending,但响应按顺序出现,恰好相隔1s,没有atCount>1。它们确实来自不同的线程。
更具体地说,控制台日志具有:
换句话说:
编辑:这表明对相同方法/路由的并发调用被序列化了。但是,通过向控制器添加第二个方法,我们很容易验证,对该方法的调用将与对第一个方法的调用同时处理,因此,用于处理请求的多线程逻辑是强制性的“开箱即用” .
因此,为了从多线程中获利,看起来应该采用传统的显式方法,例如在Executor 上以Runnable 的形式启动任何重要的工作。