java的异步执行方式 - @EnableAsync和@Async注解

异步调用的常用三种方式

异步调用是什么:

        简单来说,在开发流程中,如果我们从请求发起到数据返回结束,全程从开始到结束是需要等待的,没有其他的额外操作,这个过程可以称之为同步执行过程,在线程角度上,这一个请求任务,是一个单线程任务。

        那么异步调用,则是基于这个单线程情况下, 额外增加线程(多线程)进行任务处理。

        一般来说,常用的异步调用方式可以分为以下几种。

1、新建多线程

        可以直接在你需要异步执行任务的代码处,手动建立新线程(例如new Thread、Executor线程池等方式)。摘抄一个代码:下面这个是线程池的方式(线程池与多线程如何使用可以参考其他文章)

 /**
         * 定义一个线程池
         * 核心线程数(corePoolSize):1
         * 最大线程数(maximumPoolSize): 1
         * 保持连接时间(keepAliveTime):50000
         * 时间单位 (TimeUnit):TimeUnit.MILLISECONDS(毫秒)
         * 阻塞队列 new LinkedBlockingQueue<Runnable>()
         */
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1,5,50000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        // 执行业务逻辑方法serviceTest()
        executor.execute(new Runnable() {
            @Override
            public void run() {
                asyncService.serviceTest();
            }

2、通过配置task

        java中,还可以通过配置task至xml中(类似定时任务),在指定的请开启用task,这类方式也是异步调用,但是如果当前情况是,在某一个请求中需要立刻执行异步的话,写入task执行与写入多线程执行的效果就差不多了,代码会比较冗余!

        task如何使用这个可以自行百度,不是本次需要描述的重点。       

3、通过spring提供的@EnableAsync和@Async注解

        那么上面描述的多线程建立的方式,缺点都是:

        -- 代码量其实还是较多,如果在项目中存在多个异步方法,那么这种写法会导致很冗余,所以spring提供了一个注解式解决方案。就是Async系列的注解。

        使用注解实现异步调用,需要注意2个逻辑步骤:

        1)项目中加入@EnableAsync注解

        @EnableAsync注解可以放在项目启动类或配置类上(spring boot的主启动类就可以这样使用),这表示当前项目环境支持spring异步启用。

        

package com.pld.assetpro;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableAsync
@Slf4j
public class PldAssetProApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext application = SpringApplication.run(PldAssetProApplication.class, args);

        Environment env = application.getEnvironment();
        ...

    }

}

        2)加入@Async注解

        @Async注解可以放置于类上,会表示该类下的所有方法都会是异步的,也可以放置于指定方法上,该方法被调用时会异步执行。

       例如:

@Service
@Slf4j
@Async
public class TestForJpaServiceImpl implements ITestForJpaService {
    @Override
    public void getAsyncMsg() {
        log.info("启用异步线程执行方法");
        for (int i = 0; i < 100; i++) {
            if(i%10 == 0){
                System.out.println();
            }
            System.out.print(i+" ");
        }
        System.out.println();
    }
}

       整体代码 -- 

        接口:

package com.pld.assetpro.estate.service;

import org.springframework.scheduling.annotation.Async;

/* 测试下@async和jpa */
public interface ITestForJpaService {

    void getAsyncMsg();

}

        controller:

        

package com.pld.assetpro.estate.controller;

import com.pld.assetpro.estate.service.ITestForJpaService;
import com.pld.common.util.MsgResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@Api(value = "个人自定义测试", tags = {"控制层简单测试"})
@RequestMapping("/testCon/")
public class TestControllerForJpa {

    @Autowired
    private ITestForJpaService service;

    @PostMapping("getMsg")
    @ApiOperation(value = "test-jpa获取返回值", notes = "getMsg测试")
    public MsgResult<String> getMsg(){
        log.info("这是getMsg方法 ------------------ ");
        service.getAsyncMsg();
        log.info("getMsg方法执行完成");
        return MsgResult.successStatic("成功");
    }
}

        测试结果:可以看到getMsg方法被执行完成后,异步线程执行了service的方法(启用了多线程)。

        

        异步失效情况注意:

        通过这类注解执行异步调用,其实底层是通过生成代理对象去操作了多线程(底层就是多线程原理,运用了Executors),所以对应的,会存在一些失效情况(例如同类中,没有加@Async的方法调用了需要异步执行的@Async方法),这类失效情况与@Transactions基本雷同,可以理解为只要被调用对象的代理对象失效,那么方法执行就会变成单线程。

4、MQ

        消息队列这里不展开描述,说一下原因。

        如果面对高并发等状况,其实上面的异步调用类型中,始终是在同一个服务器下,对请求进行处理,只是开多了线程处理而已,但是还是依旧会占用服务器资源,所以不适用于高并发情况,此时我们可能就需要使用到消息队列等方案去应对了。

### 使用 `@Async` 注解实现异步调用 为了在 Java 中利用 Spring 的 `@Async` 注解来创建异步方法,需遵循特定配置实践。下面介绍具体步骤并提供实例。 #### 配置启用异步支持 要在应用程序中使用 `@Async`,首先应在启动类或配置类中标记 `@EnableAsync` 来开启异步执行的支持[^3]: ```java @Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.initialize(); return executor; } } ``` 此段代码定义了一个自定义线程池用于处理异步任务,并指定了核心线程数、最大线程数以及队列容量等参数设置。 #### 创建带有 `@Async` 的服务层组件 接下来,在希望作为异步操作的服务类上应用 `@Service` `@Async` 注解。需要注意的是,当在一个 Bean 内部通过自我调用来触发另一个同样位于该 Bean 下的方法时,代理机制无法识别到这种内部调用,因此建议采用依赖注入的方式引入当前对象实例来进行外部调用以确保异步行为正常工作[^2]。 ```java @Service public class TaskService { @Autowired private TaskService self; @Async("asyncExecutor") // 显式指定使用的执行器名称 public void performLongRunningTask(String taskName) throws InterruptedException { System.out.println(Thread.currentThread().getName()+" Start "+taskName+"..."); Thread.sleep(5000); // Simulate long-running process System.out.println(taskName + " completed."); } public void triggerTasks(){ try{ self.performLongRunningTask("Task One"); self.performLongRunningTask("Task Two"); }catch(Exception e){ log.error(e.getMessage()); } System.out.println("All tasks have been triggered!"); } } ``` 上述例子展示了两个模拟长时间运行的任务被标记为异步执行;同时提供了 `triggerTasks()` 方法展示如何安全地从同一类内发起这些异步请求而不会遇到失效问题。 #### 关键注意事项 - **跨 Service 调用**:直接在同一 service 类里调用带 `@Async` 的方法可能不生效,应考虑拆分逻辑至不同 services 或者借助于上面提到的 self-invocation 技巧。 - **返回类型**:虽然这里只讨论了 `void` 返回类型的简单情况,实际上也可以让异步方法返回 `Future<T>` 对象以便后续获取结果[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值