异步调用

场景

串行调用:在某个调用中,需要顺序调用 A, B, C三个过程方法,且是同步调用,则需要将三个方法都顺序执行完毕之后,整个调用过程执行完毕

异步调用:上述场景中,若B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了,不需要等待B方法的处理结果

方案

方案一:Java Future、ThreadPoolExecutor 来实现(有返回值用callable、无返回值用runnable)

方案二:Guava 中 AsyncEventBus

示例:

import  com.google.common.eventbus.AllowConcurrentEvents;
import  com.google.common.eventbus.AsyncEventBus;
import  com.google.common.eventbus.Subscribe;
 
import  java.util.concurrent.Executors;
 
/**
  * Created by zhuqiuhui on 2017/9/13.
  */
public  class  AsyncTest {
 
     public  static  void  main(String[] args)  throws  InterruptedException {
         AsyncEventBus asyncEventBus =  new  AsyncEventBus(Executors.newFixedThreadPool( 3 ));
         asyncEventBus.register( new  Object(){
 
             @AllowConcurrentEvents
             @Subscribe
             public  void  handleStringEvent(String s)  throws  InterruptedException {
                 Thread.sleep( 3000 );
                 System.out.println(s);
//                int test = 4/0;
                 throw  new  NullPointerException();
             }
 
             @AllowConcurrentEvents
             @Subscribe
             public  void  handleIntegerEvent(Integer i)  throws  InterruptedException {
                 Thread.sleep( 1000 );
                 System.out.println(i);
                 throw  new  IllegalStateException();
             }
 
         });
         asyncEventBus.post( "这是个测试" );
         asyncEventBus.post( 4 );
         System.out.println( "继续主函数......" );
         Thread.sleep( 5000 );
         System.out.println( "主函数结束......" );
     }
}

输出:

继续主函数......
4
九月  13 2017  5 : 05 : 42  下午 com.google.common.eventbus.EventBus$LoggingSubscriberExceptionHandler handleException
严重: Could not dispatch event: service.AsyncTest$ 1 @6fb554cc  to  public  void  service.AsyncTest$ 1 .handleIntegerEvent(java.lang.Integer)  throws  java.lang.InterruptedException
九月  13 2017  5 : 05 : 44  下午 com.google.common.eventbus.EventBus$LoggingSubscriberExceptionHandler handleException
这是个测试
严重: Could not dispatch event: service.AsyncTest$ 1 @6fb554cc  to  public  void  service.AsyncTest$ 1 .handleStringEvent(java.lang.String)  throws  java.lang.InterruptedException
主函数结束......

由上可以看出,已经注册的方法的状态不影响主流程,适合异步场景。

注意:Guava EventBus中默认订阅方法为线程不安全的,在异步调度时会自动将其包装成线程安全的方法。对于一般线程安全的实现上,可以通过@AllowConcurrentEvents注解来标识。

方案三:Spring 中 @Async 注解

 

applicationContext.xml
"annotationExecutor"  />
"annotationExecutor"  pool-size= "20" />
AsynServiceProxy.java
import  org.springframework.scheduling.annotation.Async;
import  org.springframework.scheduling.annotation.AsyncResult;
import  org.springframework.stereotype.Component;
 
import  java.util.concurrent.Future;
 
/**
  * Created by zhuqiuhui on 2017/9/14.
  */
@Component
public  class  AsynServiceProxy {
 
     @Async
     public  Future testSpringAsync01(String s) {
         try  {
             Thread.sleep( 7000 );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
 
         System.out.println(s);
         return  new  AsyncResult<>( "这是返回结果" );
     }
}
  

异步方法必须单独写一个类中

@Service
public  class  PromotionServiceImpl  implements  IPromotionService {
     @Resource
     private  AsynServiceProxy asynServiceProxy;
  
     @Override
     public  void  testAsyncMethod(){
         Future s = asynServiceProxy.testSpringAsync01( "test" );
 
         System.out.println( "继续主函数......" );
         System.out.println( "主函数结束......" );
 
 
         try  {
             System.out.println(s.get() +  "---------" );
         catch  (InterruptedException e) {
             e.printStackTrace();
         catch  (ExecutionException e) {
             e.printStackTrace();
         }
     }
}

输出结果:

输出结果
继续主函数......
主函数结束......
test
这是返回结果---------

s.get() 等待线程的结束,取得返回值。

陷阱一:Spring @Async circular reference (在方法中添加@Async出现循环依赖问题)

如service类中加:

@Service
public  class  PromotionServiceImpl  implements  IPromotionService {
     @Resource
     private  IPromotionRuleService iPromotionRuleService;
 
     @Override
     @Async
     public  void  testAsync(String s) {
         try  {
             Thread.sleep( 7000 );
         catch  (InterruptedException e) {
             e.printStackTrace();
         }
 
         System.out.println(s);
     }
}

原因:准备注入的 bean,已经是代理 bean,而不是原始 bean。

解决办法:单独写一个代理 bean,存放异步方法,并且代理 bean 中,不再依赖注入自身 bean。

注意:在同一个类中,一个方法调用另外一个有@Async注解的方法,注解是不会生效的。因为spring 在扫描包的时候,如果发现类或类中有方法有@Asycn注解,spring会为这个bean动态地生成一个代理类。当方法被调用时,实际是调用代理类的。然而,如果这个有@Async的方法是被同一个类中的其他方法调用的,那么该方法的调用就没有通过代理类,而是直接通过原来的那个bean,所以就不会启动新线程异步调用。

你可能感兴趣的:(研发管理)