微服务的异步调用_java

异步调用

一个可以无需等待被调用函数的返回值就让操作继续进行的方法。

异步调用就是你 喊 你朋友吃饭 ,你朋友说知道了 ,待会忙完去找你 ,你就去做别的了。同步调用就是你 喊 你朋友吃饭 ,你朋友在忙 ,你就一直在那等,等你朋友忙完了 ,你们一起去。

jdk1.8之前的Future

jdk并发包里的Future代表了未来的某个结果,当我们向线程池中提交任务的时候会返回该对象,可以通过future获得执行的结果,但是jdk1.8之前的Future有点鸡肋,并不能实现真正的异步,需要阻塞的获取结果,或者不断的轮询。

通常我们希望当线程执行完一些耗时的任务后,能够自动的通知我们结果,很遗憾这在原生jdk1.8之前是不支持的,但是我们可以通过第三方的库实现真正的异步回调。

public class JavaFuture {
public static void main(String[] args) throws Throwable, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> f = executor.submit(new Callable<String>() {


@Override
public String call() throws Exception {
System.out.println("task started!");
longTimeMethod();
System.out.println("task finished!");
return "hello";
}
});


//此处get()方法阻塞main线程
System.out.println(f.get());
System.out.println("main thread is blocked");
}
}

如果想获得耗时操作的结果,可以通过get()方法获取,但是该方法会阻塞当前线程,我们可以在做完剩下的某些工作的时候调用get()方法试图去获取结果。

也可以调用非阻塞的方法isDone来确定操作是否完成,isDone这种方式有点儿类似下面的过程:

微服务的异步调用_java_02

jdk1.8开始的Future

直到jdk1.8才算真正支持了异步操作,jdk1.8中提供了lambda表达式,使得java向函数式语言又靠近了一步。借助jdk原生的CompletableFuture可以实现异步的操作,同时结合lambada表达式大大简化了代码量。代码例子如下:

package netty_promise;


import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;


public class JavaPromise {
public static void main(String[] args) throws Throwable, ExecutionException {
// 两个线程的线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
//jdk1.8之前的实现方式
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
System.out.println("task started!");
try {
//模拟耗时操作
longTimeMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task finished!";
}
}, executor);


//采用lambada的实现方式
future.thenAccept(e -> System.out.println(e + " ok"));


System.out.println("main thread is running");
}
}


实现方式类似下图:


微服务的异步调用_线程池_03

Spring的异步方法

先把longTimeMethod 封装到Spring的异步方法中,这个异步方法的返回值是Future的实例。这个方法一定要写在Spring管理的类中,注意注解@Async。

@Service
public class AsynchronousService{
@Async
public Future springAsynchronousMethod(){
Integer result = longTimeMethod();
return new AsyncResult(result);
}
}

其他类调用这个方法。这里注意,一定要其他的类,如果在同类中调用,是不生效的。


@Autowired
private AsynchronousService asynchronousService;


public void useAsynchronousMethod(){
Future future = asynchronousService.springAsynchronousMethod();
future.get(1000, TimeUnit.MILLISECONDS);
}

其实Spring只不过在原生的Future中进行了一次封装,我们最终获得的还是Future实例。

ThreadPoolTaskExecutor

当我们需要实现并发、异步等操作时,通常都会使用到ThreadPoolTaskExecutor。

微服务的异步调用_线程池_04

当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

在ThreadPoolExecutor中表现为:

如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。如果创建的线程数量大于BlockQueue的最大容量,那么创建新线程来执行该任务。如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。

TaskDecorator

public interface TaskDecorator A callback interface for a decorator to be applied to any Runnable about to be executed. Note that such a decorator is not necessarily being applied to the user-supplied Runnable/Callable but rather to the actual execution callback (which may be a wrapper around the user-supplied task). The primary use case is to set some execution context around the task's invocation, or to provide some monitoring/statistics for task execution.


意思就是说这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。看上去正好可以应用于我们这种场景。多线程的场景下要多注意。

解决方案

上文中的错误信息涉及到RequestAttributes 和SecurityContext,他们都是通过ThreadLocal来保存线程数据,在同步方法中没有问题,使用线程池异步调用时,我们可以通过配合线程池的TaskDecorator装饰器拷贝上下文传递。

注意 线程池中的线程是可复用的,使用ThreadLocal需要注意内存泄露问题,所以线程执行完成后需要在finally方法中移除上下文对象。

代码如下

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


import javax.annotation.Nonnull;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;


@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean("ttlExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置线程池核心容量
executor.setCorePoolSize(20);
// 设置线程池最大容量
executor.setMaxPoolSize(100);
// 设置任务队列长度
executor.setQueueCapacity(200);
// 设置线程超时时间
executor.setKeepAliveSeconds(60);
// 设置线程名称前缀
executor.setThreadNamePrefix("ttl-executor-");
// 设置任务丢弃后的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置任务的装饰
executor.setTaskDecorator(new ContextCopyingDecorator());
executor.initialize();
return executor;
}


static class ContextCopyingDecorator implements TaskDecorator {
@Nonnull
@Override
public Runnable decorate(@Nonnull Runnable runnable) {
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
SecurityContext securityContext = SecurityContextHolder.getContext();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
runnable.run();
} finally {
SecurityContextHolder.clearContext();
RequestContextHolder.resetRequestAttributes();
}
};
}
}
}