天天看點

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
  }
}