Multithreading 意外的异步行为:Springs';s@Async vs RxJava
我正在玩Spring、RxJava和非阻塞数据处理。在我的测试应用程序中,我希望实现以下测试工作流程:Multithreading 意外的异步行为:Springs';s@Async vs RxJava,multithreading,spring-mvc,asynchronous,rx-java,Multithreading,Spring Mvc,Asynchronous,Rx Java,我正在玩Spring、RxJava和非阻塞数据处理。在我的测试应用程序中,我希望实现以下测试工作流程: [RT]接收请求 [RT]在工作线程中异步启动处理 [WT]执行一些(昂贵的)初始化工作 [WT]异步调用远程系统以获取值 [HT]执行对远程系统的请求 [HT]将响应结果转发给工作线程 [WT]使用远程系统的结果进行更多(昂贵)工作 [WT]返回最终结果 RT:请求线程(Tomcat NIO) WT:工作线程(固定大小为1、队列大小为5的线程池) HT:Hystrix线程(具有默认设置的Hy
@Async
调用WT(步骤2)和Rx的可观察的
,其余(http://localhost:9001/value
)http://localhost:9001/value-rx
)http://localhost:9002/value
是远程资源)
变量2运行得很好,但是变量1(使用@Async
)遇到了一些问题。通过分析异常、线程转储、线程池状态和日志文件,看起来ListenableFuture
(由步骤2中的@Async
服务方法返回)正在无限地阻塞线程池,线程本身正在等待。因此,RxJava无法在给定的线程池中运行所需的回调代码(步骤6)。30秒后抛出异常,整个进程失败,因为线程池仍然被阻塞,我不明白为什么
如果我多次使用variant 1,第二个(以及下面的所有请求)在步骤2中(而不是6)失败,因为线程池(size=1)仍然被ListenableFuture
(下面的堆栈跟踪)阻塞
变量2能够“同时”处理多个请求,直到队列已满,即使只有1个请求线程和1个工作线程,也不会出现问题
- 在这两种情况下,我都在使用的修改版本将
的实例映射到Observable
ListenableFuture
- 我向控制器和服务类添加了额外的日志记录。这使得更容易看到代码部分在哪个线程中执行
@Async
会导致此问题,我如何修复此问题?
代码如下:
应用程序1控制器
@ResponseBody
@RequestMapping("/value")
public ListenableFuture<String> value() {
final SettableListenableFuture<String> future;
this.app1Service.value(future);
return future;
}
application.properties:
server.port=9001
server.tomcat.max-threads=1
hystrix.command.app2.fallback.enabled=false
hystrix.command.app2.execution.isolation.thread.timeoutInMilliseconds=15000
变量1的日志输出(第一次调用)
变量2的日志输出(第一次调用)
WT的线程转储(使用变体1后)
服务
@Async
public void value(final SettableListenableFuture<String> future) {
this.doSomeStuff();
this.valueFromApp2Service().subscribe(future::set, future::setException);
}
@Async
公共无效值(最终设置可列出未来){
这个。doSomeStuff();
this.valueFromApp2Service().subscribe(future::set,future::setException);
}
我想你已经回答了你的问题(或者我遗漏了什么):
如果我多次使用变体1,则第二个(以及以下所有
由于线程
池(大小=1)仍被ListenableFuture(堆栈)阻止
跟踪如下)
您的TaskExecutor
只有一个可用线程,用于@Async
。然后,从该线程中,您希望再次使用您的TaskExecutor
作为可观察的:
this.app2Service.value().observeOn(Schedulers.from(this.taskExecutor)).subscribe(
但是,池中没有更多可用线程。如果增加coreSize,或者为RxJava文件定义不同的TaskExecutor,它应该可以工作
编辑
如果确实需要异步执行app1Service.value()
,可以从中删除@Asynch,并将任务显式提交给taskExecutor,这样就可以向ListenableFuture
添加回调。如果将结果类型更改为DeferredResult
,则可以在执行ListenableFuture
的回调时设置其结果:
@Autowired
private TaskExecutor taskExecutor;
@ResponseBody
@RequestMapping("/value")
public DeferredResult<String> value() {
final DeferredResult<String> dr = new DeferredResult<String>();
taskExecutor.execute(() -> {
final ListenableFuture<String> future = app1Service.value();
future.addCallback(new ListenableFutureCallback<String>() {
@Override
public void onSuccess(String result) {
dr.setResult(result);
}
@Override
public void onFailure(Throwable ex) {
dr.setErrorResult(ex);
}
});
});
return dr;
}
@Autowired
私有任务执行器任务执行器;
@应答器
@请求映射(“/value”)
公共延迟结果值(){
最终递延结果dr=新递延结果();
taskExecutor.execute(()->{
final ListenableFuture=app1Service.value();
future.addCallback(新ListenableFutureCallback(){
@凌驾
成功时的公共void(字符串结果){
setResult博士(结果);
}
@凌驾
失效时的公共无效(可丢弃的ex){
setErrorResult博士(ex);
}
});
});
返回dr;
}
当然,也可以使用更好的解决方案。我还介绍了变体2在相同的条件下工作,并且可以同时处理更多的请求。简单的解决方案是:我不能将ListenableFuture用作@Async方法的返回类型。Spring只是在使用将来的功能,并调用阻塞“get”方法。Spring在
AsyncExecutionInterceptor
中调用future.get()
,因为这是它唯一能做的事情,它没有其他方法来获取app1Service.value()的计算结果,从而分派请求。事实上,我认为您根本不需要@Async
,因为您已经在使用RxJava了。最好将导入添加到代码示例中。。。。
16:06:31.871 [nio-9001-exec-1] before invoke 'app1Service'
16:06:31.879 [nio-9001-exec-1] after invoke 'app1Service'
16:06:31.887 [ worker-1] before start processing
16:06:31.888 [ worker-1] do some expensive stuff
16:06:32.890 [ worker-1] finish some expensive stuff
16:06:32.891 [ worker-1] before invoke 'app2Service'
16:06:33.135 [x-App2Service-1] before invoke remote service
16:06:33.136 [x-App2Service-1] after invoke remote service
16:06:33.137 [x-App2Service-1] invoke
16:06:33.167 [ worker-1] after invoke 'app2Service'
16:06:33.172 [ worker-1] after start processing
16:07:02.816 [nio-9001-exec-1] Exception Processing ErrorPage[errorCode=0, location=/error]
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:328)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:439)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:305)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:399)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:438)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:291)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1709)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:649)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
16:07:54.465 [nio-9001-exec-1] before invoke 'app1Service'
16:07:54.472 [nio-9001-exec-1] before start processing
16:07:54.500 [nio-9001-exec-1] after start processing
16:07:54.500 [nio-9001-exec-1] after invoke 'app1Service'
16:07:54.517 [ worker-1] do some expensive stuff
16:07:55.522 [ worker-1] finish some expensive stuff
16:07:55.522 [ worker-1] before invoke 'app2Service'
16:07:55.684 [x-App2Service-1] before invoke remote service
16:07:55.685 [x-App2Service-1] after invoke remote service
16:07:55.686 [x-App2Service-1] invoke
16:07:55.717 [ worker-1] after invoke 'app2Service'
16:08:05.786 [ worker-1] next (from 'app2Service')
16:08:05.786 [ worker-1] do some more expensive stuff with 'value from app2 service'
16:08:07.791 [ worker-1] finish some more expensive stuff with 'value from app2 service'
16:08:07.791 [ worker-1] completed (from 'app2Service')
16:08:07.791 [ worker-1] next (processing)
16:08:07.792 [ worker-1] completed (processing)
"worker-1" #24 prio=5 os_prio=31 tid=0x00007fe2be8cf000 nid=0x5e03 waiting on condition [0x0000000123413000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c0d68fb0> (a org.springframework.util.concurrent.ListenableFutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at org.springframework.util.concurrent.SettableListenableFuture.get(SettableListenableFuture.java:122)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:110)
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)
Locked ownable synchronizers:
- <0x00000006c0d68170> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"worker-1" #24 prio=5 os_prio=31 tid=0x00007fc6136dd800 nid=0x5207 waiting on condition [0x000000012d638000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c02f5388> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
@ResponseBody
@RequestMapping("/value")
public ListenableFuture<String> value() {
final SettableListenableFuture<String> future;
this.app1Service.value(future);
return future;
}
@Async
public void value(final SettableListenableFuture<String> future) {
this.doSomeStuff();
this.valueFromApp2Service().subscribe(future::set, future::setException);
}
this.app2Service.value().observeOn(Schedulers.from(this.taskExecutor)).subscribe(
@Autowired
private TaskExecutor taskExecutor;
@ResponseBody
@RequestMapping("/value")
public DeferredResult<String> value() {
final DeferredResult<String> dr = new DeferredResult<String>();
taskExecutor.execute(() -> {
final ListenableFuture<String> future = app1Service.value();
future.addCallback(new ListenableFutureCallback<String>() {
@Override
public void onSuccess(String result) {
dr.setResult(result);
}
@Override
public void onFailure(Throwable ex) {
dr.setErrorResult(ex);
}
});
});
return dr;
}