代码的问题是不可能终止线程,因为它正在等待队列中的新元素,所以中断标志永远不会恢复:
正在运行代码的线程被中断。
BlockingQueue#poll()抛出InterruptedException并清除中断标志。
当标志被清除时,while循环条件(!Thread.currentThread().isInterrupted())为ture。
为防止这种行为,请始终读取InterruptedException并在方法显式(通过声明throwing InterruptedException)或隐式(通过声明/抛出原始Exception)抛出时恢复中断标志:
T getOrDefault(Callablesupplier,T defaultValue){
try{
return supplier.call();
}catch(InterruptedException e){
logger.error("Got interrupted while retrieving value.",e);
Thread.currentThread().interrupt();
return defaultValue;
}catch(Exception e){
logger.error("Got exception while retrieving value.",e);
return defaultValue;
}
}
注意使用专用的执行器进行阻塞操作
开发人员通常不希望因为一个“慢动作”而使整个服务器无响应。不幸的是,对于RPC,响应时间通常是不可预测的。
假设一台服务器有100个工作线程,并且有一个端点,它以100 RPS调用。它在内部进行RPC调用,通常需要10毫秒。在某个时间点,这个RPC的响应时间变成了2秒,而服务器在尖峰期间唯一能做的就是等待这些调用,而其他端点根本无法访问。
GET
Path("/genre/{name}")
Produces(MediaType.APPLICATION_JSON)
public Response getGenre( PathParam("name")String genreName){
Genre genre=potentiallyVerySlowSynchronousCall(genreName);
return Response.ok(genre).build();
}
解决问题的最简单方法是将进行阻塞调用的代码提交到线程池:
GET
Path("/genre/{name}")
Produces(MediaType.APPLICATION_JSON)
public void getGenre( PathParam("name")String genreName, Suspended AsyncResponse response){
response.setTimeout(1L,TimeUnit.SECONDS);
executorService.submit(()->{
Genre genre=potentiallyVerySlowSynchronousCall(genreName);
return response.resume(Response.ok(genre).build());
});
}
注意MDC值的传播
MDC(映射诊断上下文)通常用于存储单个任务的特定值。例如,在Web应用程序中,它可能为每个请求存储一个请求ID和一个用户ID,因此MDC使查找与单个请求或整个用户活动相关的日志条目变得更加容易。
不幸的是,如果代码的某些部分在专用线程池中执行,则来自提交任务的线程的MDC值不会传播。在以下示例中,第7行的日志条目包含“requestId”,而第9行的日志条目不包含:
GET
Path("/genre/{name}")
Produces(MediaType.APPLICATION_JSON)
public void getGenre( PathParam("name")String genreName, Suspended AsyncResponse response){
try(MDC.MDCCloseable ignor