Spring @Async之一:實現異步調用示例


什么是異步調用

       “異步調用”對應的是“同步調用”,同步調用指程序按照定義順序依次執行,每一行程序都必須等待上一行程序執行完成之后才能執行;異步調用指程序在順序執行時,不等待異步調用的語句返回結果就執行后面的程序。

同步調用

下面通過一個簡單示例來直觀的理解什么是同步調用:

       定義Task類,創建三個處理函數分別模擬三個執行任務的操作,操作消耗時間隨機取(10秒內)

package com.dxz.demo1;

import java.util.Random;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 定義3個任務
 */
@Component
public class Task1 {
    
    // 定義一個隨機對象.
    public static Random random = new Random();

    // 任務一;
    public void doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
    }

    // 任務二;
    public void doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
    }

    // 任務3;
    public void doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
    }
    

}

編寫一個訪問方法:

package com.dxz.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.dxz.HelloApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HelloApplication.class)
public class Task1Test {

    @Autowired
    private Task1 task1;
    
    //測試task1.
    @Test
    public void task1() throws Exception{
       task1.doTaskOne();
       task1.doTaskTwo();
       task1.doTaskThree();
    }
}

運行可以看到類似如下輸出:

開始做任務一
2017-04-28 18:02:57.397  WARN 11016 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2017-04-28 18:02:57.398  INFO 11016 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=null, acknowledgeMode=AUTO local queue size=0
完成任務一,耗時:7740毫秒
開始做任務二
完成任務二,耗時:723毫秒
開始做任務三
2017-04-28 18:03:03.415  WARN 11016 --- [cTaskExecutor-2] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2017-04-28 18:03:03.415  INFO 11016 --- [cTaskExecutor-2] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=null, acknowledgeMode=AUTO local queue size=0
完成任務三,耗時:5047毫秒

異步調用

上述的同步調用雖然順利的執行完了三個任務,但是可以看到執行時間比較長,若這三個任務本身之間不存在依賴關系,可以並發執行的話,同步調用在執行效率方面就比較差,可以考慮通過異步調用的方式來並發執行。

在Spring Boot中,我們只需要通過使用@Async注解就能簡單的將原來的同步函數變為異步函數,Task類改在為如下模式:

package com.dxz.demo1;

import java.util.Random;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 定義3個任務
 */
@Component
public class Task2 {
    
    // 定義一個隨機對象.
    public static Random random = new Random();

    // 任務一;
    @Async
    public void doTaskOne() throws Exception {
        System.out.println("開始做任務一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
    }

    // 任務二;
    @Async
    public void doTaskTwo() throws Exception {
        System.out.println("開始做任務二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
    }

    // 任務3;
    @Async
    public void doTaskThree() throws Exception {
        System.out.println("開始做任務三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
    }
    

}

  為了讓@Async注解能夠生效,還需要在Spring Boot的主程序中配置@EnableAsync,如下所示:

package com.dxz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class HelloApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}

編寫測試方法:

package com.dxz.demo1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;

import com.dxz.HelloApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HelloApplication.class)
public class Task2Test {

    @Autowired
    private Task2 task2;
    
    //測試task1.
    @Test
    public void task1() throws Exception{
       task2.doTaskOne();
       task2.doTaskTwo();
       task2.doTaskThree();
    }
}

此時可以反復執行單元測試,您可能會遇到各種不同的結果,比如:

開始做任務一
開始做任務二
開始做任務三

修改下測試類:

package com.dxz.demo1;

import java.util.concurrent.TimeUnit;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;

import com.dxz.HelloApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HelloApplication.class)
public class Task2Test {

    @Autowired
    private Task2 task2;

    // 測試task1.
    @Test
    public void task1() throws Exception {
        task2.doTaskOne();
        task2.doTaskTwo();
        task2.doTaskThree();

        System.out.println("i'm here");
        TimeUnit.SECONDS.sleep(15);
        System.out.println("over");
    }

}

 jieguo:

i'm here
開始做任務二
開始做任務一
開始做任務三
完成任務三,耗時:1280毫秒
2017-04-28 18:25:36.936 WARN 17848 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2017-04-28 18:25:36.938 INFO 17848 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=null, acknowledgeMode=AUTO local queue size=0
完成任務一,耗時:4951毫秒
完成任務二,耗時:7451毫秒
2017-04-28 18:25:42.971 WARN 17848 --- [cTaskExecutor-2] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it. Exception summary: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
2017-04-28 18:25:42.972 INFO 17848 --- [cTaskExecutor-2] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{}], channel=null, acknowledgeMode=AUTO local queue size=0
over


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM