亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

@Async注解的使用以及注解失效問題的解決

 更新時(shí)間:2024年09月19日 09:45:04   作者:豆腐腦lr  
在Spring框架中,@Async注解用于聲明異步任務(wù),可以修飾類或方法,使用@Async時(shí),必須確保方法為public,且類為Spring管理的Bean,啟用異步任務(wù)需要在主類上添加@EnableAsync注解,默認(rèn)線程池為SimpleAsyncTaskExecutor

1. @Async作用范圍

@Async的注解如下,可以看出該注解可以修飾方法

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    String value() default "";
}

該注解使用要滿足以下基本要求:

  • 1)在方法上使用該@Async注解,申明該方法是一個(gè)異步任務(wù);(必須是public的方法,不能是private的方法,否則注解會(huì)失效?。。?/strong>
  • 2)在類上面使用該@Async注解,申明該類中的所有方法都是異步任務(wù);
  • 3)方法上一旦標(biāo)記了這個(gè)@Async注解,當(dāng)其它線程調(diào)用這個(gè)方法時(shí),就會(huì)開啟一個(gè)新的子線程去異步處理該業(yè)務(wù)邏輯。
  • 4)使用此注解的方法的類對象,必須是spring管理下的bean對象 (如被@Service、@Component等修飾的Bean對象)
  • 5)要想使用異步任務(wù),需要在主類上開啟異步配置,即配置上@EnableAsync注解

2. 基本使用方法

2.1 開啟異步注解@EnableAsync

在SpringBoot的啟動(dòng)類上開啟異步任務(wù)注解

@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
    }
}

2.2 創(chuàng)建Bean對象及異步方法

@Component
public class Aservice {
    @Async
    public void MethodA() {
        System.out.println("當(dāng)前線程為:" + Thread.currentThread().getName());
    }
}

2.3 在Test方法中進(jìn)行測試

@SpringBootTest
class AsyncDemoApplicationTests {

    @Autowired
    private Aservice aservice;
    @Test
    void contextLoads() {
        System.out.println("當(dāng)前線程名稱:" + Thread.currentThread().getName());
        aservice.MethodA();
    }
}

測試結(jié)果如下,可以看到確實(shí)開啟了一個(gè)異步任務(wù)。

  • 當(dāng)前線程名稱:main
  • 當(dāng)前線程為:task-1

2.4 隱藏問題:默認(rèn)線程池配置不合適,導(dǎo)致系統(tǒng)奔潰

@Async注解在使用時(shí),如果不指定線程池的名稱,則使用Spring默認(rèn)的線程池,Spring默認(rèn)的線程池為SimpleAsyncTaskExecutor。

該類型線程池的默認(rèn)配置:

  • 默認(rèn)核心線程數(shù):8,
  • 最大線程數(shù):Integet.MAX_VALUE,
  • 隊(duì)列使用LinkedBlockingQueue,
  • 容量是:Integet.MAX_VALUE,
  • 空閑線程保留時(shí)間:60s,
  • 線程池拒絕策略:AbortPolicy。

解決方法1: 修改配置文件,指定線程池參數(shù)

通過修改SpringBoot的配置文件application.yml來解決上述問題:

spring:
  task:
    execution:
      thread-name-prefix: MyTask
      pool:
        max-size: 6
        core-size: 3
        keep-alive: 30s
        queue-capacity: 500

解決方法2:編寫配置類

首先在application.yml文件中自定義一些鍵值對。

mytask:
  execution:
    thread-name-prefix: myThread
    pool:
      max-size: 6
      core-size: 3
      keep-alive: 30
      queue-capacity: 500

然后編寫一個(gè)集成了AsyncConfig的配置類

// 如果沒有在啟動(dòng)類上加注解,在異步任務(wù)配置類中加也是可以的
@EnableAsync
@Configuration
public class AsyncExecutorConfig implements AsyncConfigurer {

    @Value(value="${mytask.execution.pool.core-size}")
    private String CORE_SIZE;

    @Value(value="${mytask.execution.pool.max-size}")
    private String MAX_SIZE;

    @Value("${mytask.execution.pool.queue-capacity}")
    private String QUEUE_SIZE;

    @Value("${mytask.execution.thread-name-prefix}")
    private String THREAD_NAME_PREFIX;

    @Value("${mytask.execution.pool.keep-alive}")
    private int KEEP_ALIVE;
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Integer.parseInt(CORE_SIZE));
        executor.setMaxPoolSize(Integer.parseInt(MAX_SIZE));
        executor.setQueueCapacity(Integer.parseInt(QUEUE_SIZE));
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.setKeepAliveSeconds(KEEP_ALIVE);
        executor.setRejectedExecutionHandler(
                (runnable, threadPoolExecutor) -> {
                    try {
                        threadPoolExecutor.getQueue().put(runnable);
                    } catch (InterruptedException e) {
                        System.out.println("Thread pool receives InterruptedException: " + e);
                    }
                });
        executor.initialize();
        return executor;
    }
}

這樣在啟動(dòng)上述任務(wù),就會(huì)打印出修改后的線程名稱。

3. 帶返回值和不帶返回值的異步任務(wù)

3.1 不帶返回值的異步任務(wù)。

AService.java中新增異步方法:

    @Async
    public void MethodB() {
        for (int i = 0; i < 5; i++) {
            // 模擬任務(wù)執(zhí)行需要5秒
            System.out.println("線程-" + Thread.currentThread().getName() + "-業(yè)務(wù)" + i + "執(zhí)行中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

為了方便測試,編寫一個(gè)Controller接口,來測試該方法。

@RestController
public class TestController {
    @Autowired
    private Aservice aservice;
    @GetMapping("/test1")
    public String test1() {
        System.out.println(Thread.currentThread().getName() + "線程開始...");
        long start = System.currentTimeMillis();
        aservice.MethodB();
        long end = System.currentTimeMillis();
        return "一共耗時(shí):" + (end -start) + "毫秒";
    }
}

在瀏覽器訪問對應(yīng)接口,發(fā)現(xiàn)僅用了幾毫秒的時(shí)間,實(shí)際MethodB的執(zhí)行時(shí)間為5秒,說明異步方法成功。

3.2 帶返回結(jié)果的異步任務(wù)。

編寫一個(gè)帶返回結(jié)果的異步任務(wù)。

    @Async
    public Future<Integer> methodC() {
        // 模擬業(yè)務(wù) 執(zhí)行需要5秒
        System.out.println("當(dāng)前線程為:" + Thread.currentThread().getName());
        Integer result = null;
        for (int i = 0; i < 5; i++) {
            // 模擬任務(wù)執(zhí)行需要5秒
            System.out.println("線程-" + Thread.currentThread().getName() + "-業(yè)務(wù)" + i + "執(zhí)行中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        result = 1; // 5秒后得到處理后的數(shù)據(jù)
        System.out.println("methodC 執(zhí)行完畢");
        return new AsyncResult<>(result);
    }

在控制層進(jìn)行調(diào)用,為了驗(yàn)證異步的效果,在控制層也加入3秒中的sleep().

    @GetMapping("/getResult")
    public Integer getResult() throws ExecutionException, InterruptedException {
        System.out.println(Thread.currentThread().getName() + "線程開始...");
        long start = System.currentTimeMillis();
        Future<Integer> future = aservice.methodC();
        Thread.sleep(3000);
        Integer result = future.get();
        long end = System.currentTimeMillis();
        System.out.println("一共耗時(shí):" + (end - start) + "毫秒");
        return result;
    }

執(zhí)行結(jié)果如下,可以看出,盡管主線程中加入了3秒的休眠,整個(gè)任務(wù)還是只用了5秒的異步任務(wù)處理時(shí)長,說明任務(wù)是在異步執(zhí)行的。

http-nio-8086-exec-1線程開始...
當(dāng)前線程為:myThread1
線程-myThread1-業(yè)務(wù)0執(zhí)行中...
線程-myThread1-業(yè)務(wù)1執(zhí)行中...
線程-myThread1-業(yè)務(wù)2執(zhí)行中...
線程-myThread1-業(yè)務(wù)3執(zhí)行中...
線程-myThread1-業(yè)務(wù)4執(zhí)行中...
methodC 執(zhí)行完畢
一共耗時(shí):5053毫秒

有些教程上面可能會(huì)直接在開啟異步任務(wù)的時(shí)候就進(jìn)行g(shù)et()了,這種方法雖然開啟了額外的線程,但主方法其實(shí)也堵塞在get()這行代碼了,相當(dāng)于就還是同步方法了。

如下:

    @GetMapping("/getResult1")
    public Integer getResult1() throws ExecutionException, InterruptedException {
        System.out.println(Thread.currentThread().getName() #43; "線程開始...");
        long start = System.currentTimeMillis();
        Integer result = aservice.methodC().get();
        Thread.sleep(3000);
        long end = System.currentTimeMillis();
        System.out.println("一共耗時(shí):" + (end - start) + "毫秒");
        return result;
    }

通過運(yùn)行結(jié)果可以看出,一共耗時(shí)8秒,如果是異步任務(wù),只需要5秒。

http-nio-8086-exec-1線程開始...
當(dāng)前線程為:myThread1
線程-myThread1-業(yè)務(wù)0執(zhí)行中...
線程-myThread1-業(yè)務(wù)1執(zhí)行中...
線程-myThread1-業(yè)務(wù)2執(zhí)行中...
線程-myThread1-業(yè)務(wù)3執(zhí)行中...
線程-myThread1-業(yè)務(wù)4執(zhí)行中...
methodC 執(zhí)行完畢
一共耗時(shí):8049毫秒

4. 注解失效的可能原因及解決方法

4.1 異步方法修飾符非public

對于異步任務(wù),要使用public修飾符

@Component
public class Aservice {
    @Async
    public void MethodA() {
        System.out.println("當(dāng)前線程為:" + Thread.currentThread().getName());
    }
}

4.2 未開啟異步配置

需要在SpringBoot啟動(dòng)類上添加@EnableAsync注解

@SpringBootApplication
@EnableAsync//開啟異步線程配置
public class AsyncDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
    }
}

或者在Aysnc配置類上添加@EnableAsync注解

// 如果沒有在啟動(dòng)類上加注解,在異步任務(wù)配置類中加也是可以的
@EnableAsync
@Configuration
public class AsyncExecutorConfig implements AsyncConfigurer {

    @Value(value="${mytask.execution.pool.core-size}")
    private String CORE_SIZE;

    @Value(value="${mytask.execution.pool.max-size}")
    private String MAX_SIZE;

    @Value("${mytask.execution.pool.queue-capacity}")
    private String QUEUE_SIZE;

    @Value("${mytask.execution.thread-name-prefix}")
    private String THREAD_NAME_PREFIX;

    @Value("${mytask.execution.pool.keep-alive}")
    private int KEEP_ALIVE;
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Integer.parseInt(CORE_SIZE));
        executor.setMaxPoolSize(Integer.parseInt(MAX_SIZE));
        executor.setQueueCapacity(Integer.parseInt(QUEUE_SIZE));
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.setKeepAliveSeconds(KEEP_ALIVE);
        executor.setRejectedExecutionHandler(
                (runnable, threadPoolExecutor) -> {
                    try {
                        threadPoolExecutor.getQueue().put(runnable);
                    } catch (InterruptedException e) {
                        System.out.println("Thread pool receives InterruptedException: " + e);
                    }
                });
        executor.initialize();
        return executor;
    }
}

4.3 同一個(gè)類的普通方法調(diào)用異步方法

如果在一個(gè)類中,方法A被@Async修飾,而方法B沒有被@Async修飾,并且方法B調(diào)用了方法A,那么會(huì)導(dǎo)致@Async修飾的方法A的注解失效。原因是,對于對于加了@Async的方法A是通過SpringAOP機(jī)制生成的代理類執(zhí)行的,方法B是直接調(diào)用這個(gè)類的方法,因此通過B調(diào)用A,會(huì)使得A也被Spring當(dāng)成普通方法直接調(diào)用,從而使得注解失效。

可以通過以下兩種方式來確保@Async注解生效:

方法1: 將方法A的調(diào)用放在另外一個(gè)Bean上,并通過依賴注入的方式使用該Bean。

@Component
public class MyClass {
    private final MyAsyncService myAsyncService;

    public MyClass(MyAsyncService myAsyncService) {
        this.myAsyncService = myAsyncService;
    }

    @Async
    public void A() {
        // 異步操作內(nèi)容
    }

    public void B() {
        myAsyncService.A();
    }
}

@Service
public class MyAsyncService {
    @Async
    public void A() {
        // 異步操作內(nèi)容
    }
}

在上述示例中,MyClass類中的方法B調(diào)用了MyAsyncService類中的方法A。由于MyClass類和MyAsyncService類是不同的Bean,在MyClass中直接調(diào)用myAsnycService.A()時(shí),會(huì)觸發(fā)異步操作。

方法2. 在同一個(gè)類內(nèi)部使用self-invocation的方式來調(diào)用被@Async修飾的方法。

@Service
public class MyService {
    @Autowired
    private MyService self;

   @Async
   public void A() {
       // 異步操作內(nèi)容
   }

   public void B() {
       self.A(); // 使用self-invocation調(diào)用被@Async修飾的方法A()
   }
}

在上述示例中,MyService類內(nèi)部使用@Autowired將自身注入到了self變量中,在B()方法中通過self.A()來調(diào)用被@Async修飾的A()方法。這樣可以繞過Spring代理機(jī)制,保證A()方法能夠以異步方式執(zhí)行。

無論采取哪種方式,都能確保被@Asnyc修飾的方法在調(diào)用時(shí)能夠以異步方式執(zhí)行,而非直接在當(dāng)前線程執(zhí)行

上述代碼,其實(shí)存在一個(gè)問題,即:因?yàn)?code>MyService類中使用了自身的實(shí)例作為依賴。這種情況下,使用@Autowired注入會(huì)導(dǎo)致循環(huán)依賴。解決這個(gè)問題有幾種方法:

  1. 使用@Lazy注解:將依賴的注入方式改為懶加載模式,即在需要使用時(shí)才進(jìn)行實(shí)例化。您可以將@Autowired注解改為@Autowired @Lazy,以解決循環(huán)依賴的問題。
@Service
public class MyService {
    @Autowired
    @Lazy
    private MyService self;

   @Async
   public void A() {
       // 異步操作內(nèi)容
   }

   public void B() {
       self.A(); // 使用self-invocation調(diào)用被@Async修飾的方法A()
   }
}
  1. 使用構(gòu)造函數(shù)注入:將依賴通過構(gòu)造函數(shù)進(jìn)行注入而不是字段注入。這樣可以避免循環(huán)依賴,因?yàn)樵跇?gòu)造對象時(shí)就能明確傳遞依賴關(guān)系。
@Service
public class MyService {
    private final MyService self;

    @Autowired
    public MyService(MyService self) {
        this.self = self;
    }

   @Async
   public void A() {
       // 異步操作內(nèi)容
   }

   public void B() {
       self.A(); // 使用self-invocation調(diào)用被@Async修飾的方法A()
   }
}

至于用哪種方法,可以根據(jù)實(shí)際需求選擇適合你場景的解決方案。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Freemarker如何生成樹形導(dǎo)航菜單(遞歸)

    Freemarker如何生成樹形導(dǎo)航菜單(遞歸)

    這篇文章主要為大家詳細(xì)介紹了Freemarker采用的的方法生成樹形導(dǎo)航菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • javaDSL簡單實(shí)現(xiàn)示例分享

    javaDSL簡單實(shí)現(xiàn)示例分享

    DSL領(lǐng)域定義語言,用來描述特定領(lǐng)域的特定表達(dá)。比如畫圖從起點(diǎn)到終點(diǎn);路由中的從A到B。這是關(guān)于畫圖的一個(gè)簡單實(shí)現(xiàn)
    2014-03-03
  • 基于Java中最常用的集合類框架之HashMap(詳解)

    基于Java中最常用的集合類框架之HashMap(詳解)

    下面小編就為大家?guī)硪黄贘ava中最常用的集合類框架之HashMap(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • 基于Properties類操作.properties配置文件方法總結(jié)

    基于Properties類操作.properties配置文件方法總結(jié)

    這篇文章主要介紹了Properties類操作.properties配置文件方法總結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Springboot 整合 Java DL4J 打造文本摘要生成系統(tǒng)

    Springboot 整合 Java DL4J 打造文本摘要生成系統(tǒng)

    本文介紹了如何使用SpringBoot整合JavaDeeplearning4j構(gòu)建文本摘要生成系統(tǒng),該系統(tǒng)能夠自動(dòng)從長篇文本中提取關(guān)鍵信息,生成簡潔的摘要,幫助用戶快速了解文本的主要內(nèi)容,技術(shù)實(shí)現(xiàn)包括使用LSTM神經(jīng)網(wǎng)絡(luò)進(jìn)行模型構(gòu)建和訓(xùn)練,并通過SpringBoot集成RESTfulAPI接口
    2024-11-11
  • Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目

    Quarkus的Spring擴(kuò)展快速改造Spring項(xiàng)目

    這篇文章主要為大家介紹了Quarkus的Spring項(xiàng)目擴(kuò)展,帶大家快速改造Spring項(xiàng)目示例演繹,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-02-02
  • java排查進(jìn)程占用系統(tǒng)內(nèi)存高方法

    java排查進(jìn)程占用系統(tǒng)內(nèi)存高方法

    這篇文章主要為大家介紹了java進(jìn)程占用系統(tǒng)內(nèi)存高排查方法,
    2023-06-06
  • IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文)

    IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文)

    這篇文章主要介紹了IntelliJ IDEA(2019)安裝破解及HelloWorld案例(圖文),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Maven項(xiàng)目修改JDK版本全過程

    Maven項(xiàng)目修改JDK版本全過程

    這篇文章主要介紹了Maven項(xiàng)目修改JDK版本全過程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Spring加載properties文件的方法

    Spring加載properties文件的方法

    這篇文章主要為大家詳細(xì)介紹了Spring加載properties文件的兩種方法,一是通過xml方式,另一種方式是通過注解方式,感興趣的小伙伴們可以參考一下
    2016-06-06

最新評論