開發與維運

SQI中@Async异步注解简单使用

最近在Review代码中看到有些同学在使用多线程的时候比较随意,基本上各种使用方式都有。但是有些写法其实是适合standalone的程序,有的写法在Spring中得到了简化。所以建议大家在在SQI相关开发中可以尝试用@Async注解的方式完成异步操作,既清晰明了,也简单方便。

异步的几种写法

  • 刀耕火种年代

    这个年代是没有线程池管理的,实现了Runnable接口后就start来运行。

  • 大航海时代

    这个年代已经开始使用线程池了,我们可以看一段示例代码

      ExecutorService executor = Executors.newFixedThreadPool(N);
      for (int i = 0; i < kProducts.size(); i++) {
          Products product = kProducts.get(i);
    if (pid != null || bizdate != null) {
        if (bizdate == null) {
            bizdate = "2015-04-01";
        }
    executor.execute(new offlineKeludeJob(product, DateUtils.getMorning(DateUtils.parseDate(bizdate))));
             } else {
        executor.execute(new offlineKeludeJob(product));
    }
      }
      executor.shutdown();

    这段代码在standalone程序中看起来问题不大。但是在Web服务中每个Request都会来调用一次,会使得堆积的请求处理队列消耗非常大的内存,极端情况下会引发OOM问题。

  • 工业化时代

在这里大家使用Spring中的ThreadPoolTaskExecutor,使用起来也没有特别大的问题。例如下面这个简单的例子

   <task:executor id="labelTaskExecutor" pool-size="100" queue-capacity="10000" rejection-policy="ABORT"/>
   @Autowired
   ThreadPoolTaskExecutor labelDbExecutor;
   labelTaskExecutor.submit(new Runnable() {
       @Override
       public void run() {
           try {
               Integer ret = insert(tmpLabelData);
               if (ret <= 0) {
                   logger.error(String.format("[ERRORLabelData]insert data failed! label data:%s", JSONObject.toJSONString(tmpLabelData)));
                    }
              } catch (Exception e) {
                  logger.error(String.format("[ERRORLabelData]insert data into db exception!"), e);
               }
           }
      }
   );

@Async注解使用

上面说了那么多用户,其实都没有什么用。只是用来突出关于Async注解的使用。最初的两种方法都因为自身的不足,不建议在SQI项目中直接使用。而最后一个方法虽然没有什么问题,但是写起来比较麻烦,还需要自己实现Runnable接口。那么对于我们来说,有没有更简单方便的方式来使用呢?答案就是Spring 3.0带来Async注解,其使用主要就只有两步。

  • 在applicationContext.xml中增加线程池配置

    <context:component-scan base-package="com.alibaba.search" />
    <task:annotation-driven executor="defaultExecutor"/>
    <task:executor id="defaultExecutor" pool-size="100" queue-capacity="10000" rejection-policy="ABORT"/>
  • 在你需要调用的方法上加上相应的注解

    @Service
    public class TestClass {
      @Async
      public void asyncTest() {
        //Do something
      }
    }

@Async使用注意事项

  • 类内部的方法间的调用是不起效果的,具体是在于Async是基于bean自动扫描后生成的proxy实现的。那么一定要用怎么办,最简单的办法就是拆出到另外一个class里面。
  • 如果在多个XML中进行了配置是会覆盖的。例如SQI在web.xml中定义了

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMvcConfig.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

那么如果同时在springMvcConfig.xml和applicationContext.xml中有对task:executor的定义的话,DispatcherServlet中加载的XML会覆盖掉ContextLoaderListener中加载的。用我们的例子来讲就是springMvcConfig.xml中的配置会覆盖掉applicationContext.xml。

  • 如果要使用多个线程池怎么办?在使用的时候指定一下对应的ID就可以了。

    <context:component-scan base-package="com.alibaba.search" />
    <task:annotation-driven executor="defaultExecutor"/>
    <task:executor id="defaultExecutor" pool-size="100" queue-capacity="10000" rejection-policy="ABORT"/>
    <task:executor id="statisticExecutor" pool-size="100" queue-capacity="10000" rejection-policy="ABORT"/>
@Service
public class TestClass {
  @Async
  public void asyncTest() {
    //Do something
  }
  @Async("statisticExecutor")
  public void statisticAsyncTest() {
    //Do some statistic
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *