1. 程式人生 > 其它 >SpringBoot中@Async非同步的使用及非同步與同步的區別

SpringBoot中@Async非同步的使用及非同步與同步的區別

簡介

在開發過程中,非同步是提升系統併發能力的一個重要利器。而 spring 中的 @Async 非同步註解,使我們能夠非常方便地實現方法地非同步呼叫。接下來主要結合以下幾個問題來講述 java 程式中的非同步的使用:

  • 什麼是同步
  • 什麼是非同步,以及非同步的作用
  • 如何在 SpringBoot 中使用非同步

1、什麼是同步

同步呼叫,是遵循的順序處理,一個一個呼叫

特徵:在呼叫多個方法的時候,強調先後順序,執行完一個方法再執行下一個方法,方法與方法之間不可調換先後順序,不會並行執行

示例:

package com.example.demo.test1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.Future;

@Component
@Slf4j
public class SyncDemo {

    public void test1() throws Exception {
        log.info("test1開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1000);
        Long end = System.currentTimeMillis();
        log.info("test1執行結束");
        log.info("test1執行時長:{}毫秒", end-start);
    }

    public void test2() throws Exception {
        log.info("test2開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1500);
        Long end = System.currentTimeMillis();
        log.info("test2執行結束");
        log.info("test2執行時長:{}毫秒", end-start);
    }

    public void test3() throws Exception {
        log.info("test3開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(2000);
        Long end = System.currentTimeMillis();
        log.info("test3執行結束");
        log.info("test3執行時長:{}毫秒", end-start);
    }

    public static void main(String[] args) throws Exception {
        SyncDemo syncDemo = new SyncDemo();
        Long start = System.currentTimeMillis();
        syncDemo.test1();
        syncDemo.test2();
        syncDemo.test3();
        Long end = System.currentTimeMillis();
        log.info("總執行時長:{}毫秒", end-start);
    }

}

執行結果:

總執行時長,是test1(),test2(),test3() 三個方法的近似總和

2、什麼是非同步

非同步主要是呼叫方法後,不用等方法執行完畢就直接返回。非同步方法會與其他方法並行執行,以此來節約時間,提高程式的併發能力。

示例:

為準確統計所有非同步方法執行時間,現在將方法的返回物件,改為 Future,以此來可以判斷方法是否執行完畢

示例程式碼如下:

IAsyncDemoService.java

package com.example.demo.test1;

import java.util.concurrent.Future;

public interface IAsyncDemoService {

    public Future<String> test1()  throws Exception ;

    public Future<String> test2()  throws Exception ;

    public Future<String> test3()  throws Exception ;

}

AsyncDemoServiceImpl.java

package com.example.demo.test1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;

@Service
@Slf4j
public class AsyncDemoServiceImpl implements IAsyncDemoService {

    @Async
    @Override
    public Future<String> test1() throws Exception {
        log.info("test1開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1000);
        Long end = System.currentTimeMillis();
        log.info("test1執行結束");
        log.info("test1執行時長:{}毫秒", end-start);
        return new AsyncResult<>("test1完成");
    }

    @Async
    @Override
    public Future<String> test2() throws Exception {
        log.info("test2開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1500);
        Long end = System.currentTimeMillis();
        log.info("test2執行結束");
        log.info("test2執行時長:{}毫秒", end-start);
        return new AsyncResult<>("test2完成");
    }

    @Async
    @Override
    public Future<String> test3() throws Exception {
        log.info("test3開始執行");
        Long start = System.currentTimeMillis();
        Thread.sleep(2000);
        Long end = System.currentTimeMillis();
        log.info("test3執行結束");
        log.info("test3執行時長:{}毫秒", end-start);
        return new AsyncResult<>("test3完成");
    }
}

DemoApplicationTests.java

package com.example.demo;

import com.example.demo.test1.IAsyncDemoService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.Future;

@SpringBootTest
@Slf4j
class DemoApplicationTests {

    @Autowired
    IAsyncDemoService asyncDemoService;

    @Test
    void contextLoads() throws Exception{
        Long start = System.currentTimeMillis();
        Future task1 = asyncDemoService.test1();
        Future task2 = asyncDemoService.test2();
        Future task3 = asyncDemoService.test3();

        while (true) {
            if (task1.isDone() && task2.isDone() && task3.isDone()) {
                break;
            }
        }
        Long end = System.currentTimeMillis();
        log.info("總執行時長:{}毫秒", end-start);
    }

}

執行結果如下:總執行時間與執行最長的 test3()方法的時間相近

結論:非同步方法是通過形式,呼叫後即返回,具體方法的執行,不影響其他方法的執行,多個方法並行的方式實現。

3、@Async 失效的情況

在使用 註解@Async 的時候,一定要注意規避以下情況,會造成 註解失效的情況

  • 註解@Async的方法不是public方法

  • 註解@Async的返回值只能為void或Future

  • 註解@Async方法使用static修飾也會失效

  • spring無法掃描到非同步類,沒加註解@Async或@EnableAsync註解

  • 呼叫方與被呼叫方不能在同一個類

  • 類中需要使用@Autowired或@Resource等註解自動注入,不能自己手動new物件

  • 在Async方法上標註@Transactional是沒用的.但在Async方法呼叫的方法上標註@Transcational是有效的

4、非同步執行緒池的設定

使用 @Async 來實現非同步,預設使用Spring建立ThreadPoolTaskExecutor,預設執行緒池的引數設定為:

  • 預設心執行緒數:8,

  • 最大執行緒數:Integet.MAX_VALUE,

  • 佇列使用LinkedBlockingQueue,

  • 容量是:Integet.MAX_VALUE,

  • 空閒執行緒保留時間:60s,

  • 執行緒池拒絕策略:AbortPolicy。

問題:_
_併發情況下,會無限建立執行緒,導致系統資源耗盡,所以我們要手動設定執行緒池的配置,來避免無限建立執行緒的情況。

配置如下:
application.yaml

spring:
  task:
    execution:
      pool:
        max-size: 6
        core-size: 3
        keep-alive: 3s
        queue-capacity: 1000

自定義執行緒池配置類:

ExecutorConfig.java

@Configuration
@Data
public class ExecutorConfig{

    /**
     * 核心執行緒
     */
    private int corePoolSize;

    /**
     * 最大執行緒
     */
    private int maxPoolSize;

    /**
     * 佇列容量
     */
    private int queueCapacity;

    /**
     * 保持時間
     */
    private int keepAliveSeconds;

    /**
     * 名稱字首
     */
    private String preFix;

    @Bean
    public Executor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

使用方式:

在使用 @Async 的時候,指定執行緒池為自定義執行緒池 myExecutor

例如:

    @Async("myExecutor")
    public void test1() throws Exception {

    }