关于java实现无感知异步处理的一些想法

1,336 阅读3分钟

说出来你们可能不相信,上期预告的《ssl?tsl?有关于网络安全》被我不小心暂时咕掉了(什么叫咕掉了,读书人的咕能叫咕么!下期,下期我一定把这个坑填上,我发四)。原因呢很复杂,主要是我有一个大胆的想法,想赶紧写下来。首先呢,这是一篇随笔,里面讨论的东西只是突然的一个不太成熟的想法。欢迎大家来指正。

先说说背景,最近偶然间随手用koa写了几个小东西(这玩意真的很好用啊),被里面的await,async强势吸了一波粉。刚好也在整理以前写的java代码,发现很多接口都是在同步处理的过程中做了很多乱七八糟的事情,使得性能变得很低。而对这些东西做优化又会修改到原先的代码,从而产生问题。于是突发奇想能不能在java里也实现这么一个东西,只需要声明一下就能将方法从同步切成异步而不侵入原先的代码。

实现方式

我初步想到的方式是通过aop+注解的方式去实现。先定义一个注解,通过注解切点的方式去编织一个切面,在这个切面中使用将调用的方法放到一个线程池里去执行。

/**
* 创建一个注解类
*/
package com.async.service.annotation;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Async {

}

/**
* 根据注解切点编制切面
*/
@Aspect
@Component
public class AsyncAspect{
    
    private static final ExecutorService threadPool = Executors.newCachedThreadPool();
    
    @Around("@annotation(com.async.service.annotation.Async)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        threadPool.execute(() -> {
            try {
                pjp.proceed();
            }  catch (Exception e){
                throw e;
            }  catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        })
        return null;
    }
}

于是在我们原先的代码里

@Service
public class ServiceImpl implements Iservice{

    @Autowired
    private IBusinessService businessService;
    
    @Override
    public void run(){
        Object res1 = businessService.runBusiness1();
        businessService.runBusiness2();
        Object res3 = businessService.runBusiness3();
        Object res4 = businessService.runBusiness4(res1,res3);
    }
}

像businessService.runBusiness2()这种返回结果不影响后续执行的业务处理方法就可以直接使用先前我们创建的注解去标记。

@Service
public class BusinessServiceImpl implements IBusinessService{
    @Override
    public void runBusiness1(){.....}
    @Async
    @Override
    public void runBusiness2(){.....}
    @Override
    public void runBusiness3(){.....}
    @Override
    public void runBusiness4(Object res1,Object res3){.....}
}

然而这种实现方式只能针对返回结果不影响后续执行的情况可以使用,应用面还不是很广。所以需要对他进行进一步优化。使得runBusiness1也能使用。 于是我们对于切面做出如下修改

@Aspect
@Component
public class AsyncAspect{
    
    private static final ExecutorService threadPool = Executors.newCachedThreadPool();
    
    private static final ThreadLocal<Future> callbackNext = new ThreadLocal<>();
    
    @Around("@annotation(com.async.service.annotation.Async)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //threadPool.execute(() -> {
          Future<Object> callback = threadPool.submit(new Callable<Object>(){
                    @Override
                    public Object call() throws Exception {
                        try {
                            return pjp.proceed();
                        } catch (Exception e){
                            throw e;
                        } catch (Throwable throwable) {
                            throwable.printStackTrace();
                        }
                        return null;
                    }
                });
        callbackNext.set(res);
        return null;
    }
    
    public static Future next(){
        return callbackNext.get();
    }
}

在这里我们将原先线程池提交线程的方法从execute修改为了submit。这样的话我们就可以获取到一个Future对象,使原来执行方法的结果能通过Future对象来让我们的程序获取到。而Future对象的传递,为了解决并发问题,在这里使用了ThreadLocal去做了一个传递功能。于是我们的业务代码就可以变成。。

@Service
public class ServiceImpl implements Iservice{

    @Autowired
    private IBusinessService businessService;
    
    @Override
    public void run(){
        businessService.runBusiness1();
		Future<Object> callback = AsyncAspect.next();
        businessService.runBusiness2();
        res3 = businessService.runBusiness3();
        Object res4 = businessService.runBusiness4(callback.get(),res3);
    }
}

这样以后,在获取res3方法的时候我们就可以使用多线程并行计算res1了。

哎?卧槽等等好像哪里不对的样子,怎么写着写着就变成并行计算了!

最后

总的来说上述实现只是我现在的一种设想,其中的很多的实现和规范都不是很完美。觉得哪里有问题的和改进的欢迎各位大佬们来喷,蟹蟹。

本期的内容就讲到这里了,我是 IHAP亚楠小萌新,更多精彩内容,尽在 ihap 技术黑洞。