一、前言

异步执行对于我们日常的开发来说并不陌生,在实际的开发过程中,很多场景都会使用到异步,相比于同步执行,异步可以大大缩短请求链路耗时时间,提高运行效率。

二、线程Thread

package async;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
public class AsyncThread extends Thread {
    @Override
    public void run() {
        System.out.println("异步执行");
        System.out.println("current thread:" + Thread.currentThread().getName());
    }
}

测试:

package async;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
public class AsyncTest {
    public static void main(String[] args) {
        AsyncThread thread = new AsyncThread();
        thread.start();
    }
}

Java实现异步编程的几种方式_异步任务

如果我们每次使用多线程都创建一个Thread,频繁的创建、销毁很浪费系统资源,我们可以采用线程池来处理。

package async;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
public class AsyncTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(() -> System.out.println("current thread:" + Thread.currentThread().getName()));
        System.out.println("main thread:" + Thread.currentThread().getName());
    }
}

执行程序:

Java实现异步编程的几种方式_异步任务_02

三、Future异步

package async;

import java.util.concurrent.*;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
public class AsyncTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(() -> {
            System.out.println("异步操作...");
            return "async result";
        });
        String result = future.get();
        System.out.println("异步返回的结果:" + result);
        System.out.println("main thread:" + Thread.currentThread().getName());
    }
}

执行程序:

Java实现异步编程的几种方式_CompletableFuture_03

Future的不足之处的包括以下几点:

1️⃣ 无法被动接收异步任务的计算结果:虽然我们可以主动将异步任务提交给线程池中的线程来执行,但是待异步任务执行结束之后,主线程无法得到任务完成与否的通知,它需要通过get方法主动获取任务执行的结果。

2️⃣ Future件彼此孤立:有时某一个耗时很长的异步任务执行结束之后,你想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future都是彼此之间都是孤立的,所以才有了后面的CompletableFutureCompletableFuture就可以将多个Future串联起来形成任务流。

3️⃣ Futrue没有很好的错误处理机制:截止目前,如果某个异步任务在执行发的过程中发生了异常,调用者无法被动感知,必须通过捕获get方法的异常才知晓异步任务执行是否出现了错误,从而在做进一步的判断处理。

四、CompletableFuture异步

package async;

import java.util.concurrent.*;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
public class AsyncTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            try {
                System.out.println("异步任务1");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            try {
                System.out.println("异步任务2");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        // 异步执行
        CompletableFuture.allOf(future1, future2).join();

        System.out.println("耗时:" + (System.currentTimeMillis() - start));
    }
}

执行结果:

返回耗时最长的异步任务。

Java实现异步编程的几种方式_@Async_04

CompletableFuture内部使用了ForkJoinPool来处理异步任务。

五、Spring的@Async异步

1.需要在启动类加上@EnableAsync注解

package com.example.spepcdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpepcDemoApplication {


    public static void main(String[] args) {
        SpringApplication.run(SpepcDemoApplication.class, args);

    }

}

2.创建自定义异步线程池

package com.example.spepcdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * @author qx
 * @date 2024/5/28
 * @des 自定义异步线程池
 */
@Configuration
public class TaskPoolConfig {

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程池大小
        executor.setCorePoolSize(16);
        //最大线程数
        executor.setMaxPoolSize(20);
        //配置队列容量,默认值为Integer.MAX_VALUE
        executor.setQueueCapacity(99999);
        //活跃时间
        executor.setKeepAliveSeconds(60);
        //线程名字前缀
        executor.setThreadNamePrefix("asyncServiceExecutor -");
        //设置此执行程序应该在关闭时阻止的最大秒数,以便在容器的其余部分继续关闭之前等待剩余的任务完成他们的执行
        executor.setAwaitTerminationSeconds(60);
        //等待所有的任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

2.创建异步服务类

package com.example.spepcdemo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
@Service
public class AsyncService {


    /**
     * 异步任务1
     */
    @Async
    public void async1() throws InterruptedException {
        System.out.println("异步执行操作1");
        Thread.sleep(2000);
    }

    /**
     * 异步任务2
     */
    @Async
    public void async2() throws InterruptedException {
        System.out.println("异步执行操作2");
        Thread.sleep(3000);
    }
}

3.创建测试控制层

package com.example.spepcdemo.controller;

import com.example.spepcdemo.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2024/5/28
 * @des 异步操作
 */
@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public void testAsync() throws InterruptedException {
        long start = System.currentTimeMillis();
        //调用两个异步方法
        asyncService.async1();
        asyncService.async2();
        System.out.println("完成操作,耗时:" + (System.currentTimeMillis() - start));
    }
}

4.启动查询测试

我们在浏览器访问http://localhost:8080/async

Java实现异步编程的几种方式_@Async_05

六、Hutool的ThreadUtil异步工具类

1.引入依赖

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>5.8.27</version>
</dependency>

2.异步服务类

package com.example.spepcdemo.service;

import cn.hutool.core.thread.ThreadUtil;
import org.springframework.stereotype.Service;

/**
 * @author qx
 * @date 2024/5/28
 * @des
 */
@Service
public class AsyncService {


    /**
     * 异步任务1
     */
    public void async1() {
        ThreadUtil.execAsync(() -> {
            System.out.println("异步执行操作1");
            ThreadUtil.sleep(2000);
        });

    }

    /**
     * 异步任务2
     */
    public void async2() {
        ThreadUtil.execAsync(() -> {
            System.out.println("异步执行操作2");
            ThreadUtil.sleep(3000);
        });
    }
}

3.测试控制层

package com.example.spepcdemo.controller;

import com.example.spepcdemo.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author qx
 * @date 2024/5/28
 * @des 异步操作
 */
@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public void testAsync() {
        long start = System.currentTimeMillis();
        //调用两个异步方法
        asyncService.async1();
        asyncService.async2();
        System.out.println("完成操作,耗时:" + (System.currentTimeMillis() - start));
    }
}

4.启动程序测试

Java实现异步编程的几种方式_@Async_06