详解java开启异步线程的几种方法(@Async,AsyncManager,线程池)

 更新时间:2023年09月18日 11:11:05   作者:天河归来  
在springboot框架中,可以使用注解简单实现线程的操作,还有AsyncManager的方式,如果需要复杂的线程操作,可以使用线程池实现,本文通过实例代码介绍java开启异步线程的几种方法(@Async,AsyncManager,线程池),感兴趣的朋友一起看看吧

整体描述

在java中异步线程很重要,比如在业务流处理时,需要通知硬件设备,发短信通知用户,或者需要上传一些图片资源到其他服务器这种耗时的操作,在主线程里处理会阻塞整理流程,而且我们也不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。最近在项目中使用了很多线程的操作,在这做个记录。

实现方法

线程的操作,是java中最重要的部分之一,实现线程操作也有很多种方法,这里仅介绍几种常用的。在springboot框架中,可以使用注解简单实现线程的操作,还有AsyncManager的方式,如果需要复杂的线程操作,可以使用线程池实现。下面根据具体方法进行介绍。

一、注解@Async

springboot框架的注解,使用时也有一些限制,这个在网上也有很多介绍,@Async注解不能在类本身直接调用,在springboot框架中,可以使用单独的Service实现异步方法,然后在其他的类中调用该Service中的异步方法即可,具体如下:

1. 添加注解

在springboot的config中添加 @EnableAsync注解,开启异步线程功能

package com.thcb.boot.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
/**
 * MyConfig
 *
 * @author thcb
 */
@Configuration
@EnableAsync
public class MyConfig {
    // 自己配置的Config
}

2. 创建异步方法Service和实现类

使用service实现耗时的方法

Service类:

package com.thcb.execute.service;
import org.springframework.scheduling.annotation.Async;
/**
 * IExecuteService
 *
 * @author thcb
 */
public interface IExecuteService {
    /**
     * 一些耗时的操作,使用单独线程处理
     * 这里就简单写了一个sleep5秒的操作
     */
    @Async
    public void sleepingTest();
}

Service实现类:

package com.thcb.execute.service.impl;
import com.thcb.execute.service.IExecuteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
 * ExecuteService业务层处理
 *
 * @author thcb
 */
@Service
public class ExecuteServiceImpl implements IExecuteService {
    private static final Logger log = LoggerFactory.getLogger(ExecuteServiceImpl.class);
    @Override
    public void sleepingTest() {
        log.info("SleepingTest start");
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            log.error("SleepingTest:" + e.toString());
        }
        log.info("SleepingTest end");
    }
}

3. 调用异步方法

这里根据Springboot的框架,在controller层调用,并使用log查看是否时异步结果。

controller:

package com.thcb.boot.controller;
import com.thcb.execute.service.IExecuteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * TestController
 *
 * @author thcb
 */
@RestController
public class TestController {
    private static final Logger log = LoggerFactory.getLogger(TestController.class);
    @Autowired
    private IExecuteService executeService;
    @RequestMapping("/test")
    public String test() {
        return "spring boot";
    }
    @RequestMapping("/executeTask")
    public String executeTask() {
        log.info("executeTask Start!");
        executeService.sleepingTest();
        log.info("executeTask End!");
        return "executeTask";
    }
}

在log查看结果:

log结果

接口直接返回了executeTask,并log出executeTask End!在5秒之后,log打出SleepingTest end,说明使用了异步线程处理了executeService.sleepingTest的方法。

二、AsyncManager

使用AsyncManager方法,也是SpringBoot框架中带的任务管理器,可以实现异步线程。

1. 创建AsyncManager类

使用AsyncManager首先需要创建一个AsyncManager类,这个在springboot框架中应该也是有的:

/**
 * 异步任务管理器
 *
 * @author thcb
 */
public class AsyncManager {
    /**
     * 操作延迟10毫秒
     */
    private final int OPERATE_DELAY_TIME = 10;
    /**
     * 异步操作任务调度线程池
     */
    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
    /**
     * 单例模式
     */
    private AsyncManager() {
    }
    private static AsyncManager me = new AsyncManager();
    public static AsyncManager me() {
        return me;
    }
    /**
     * 执行任务
     *
     * @param task 任务
     */
    public void execute(TimerTask task) {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }
    /**
     * 停止任务线程池
     */
    public void shutdown() {
        Threads.shutdownAndAwaitTermination(executor);
    }
}

2. 创建一个耗时的操作类

这里同样需要创建一个耗时的操作,也是用sleep模拟:

    public TimerTask sleepingTest() {
        return new TimerTask() {
            @Override
            public void run() {
            	// 耗时操作
            	try {
                	Thread.sleep(5000);
        		} catch (Exception e) {
            		log.error("SleepingTest:" + e.toString());
        		}
            }
        };
    }

3. 执行异步操作

使用AsyncManager执行异步操作也比较简单,直接调用即可:

// 异步线程池
    AsyncManager.me().execute(sleepingTest());

三、线程池

使用线程池可以设定更多的参数,线程池在网上也有很多详细的介绍,在这我只介绍一种,带拒绝策略的线程池。

1. 创建线程池

创建带有拒绝策略的线程池,并设定核心线程数,最大线程数,队列数和超出核心线程数量的线程存活时间:

    /**
     * 线程池信息: 核心线程数量5,最大数量10,队列大小20,超出核心线程数量的线程存活时间:30秒, 指定拒绝策略的
     */
    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(20), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            log.error("有任务被拒绝执行了");
        }
    });

2. 创建一个耗时的操作类

由于线程池需要传入一个Runnable,所以此类继承Runnable,还是用sleep模拟耗时操作。

    /**
     * 耗时操作
     */
    static class MyTask implements Runnable {
        private int taskNum;
        public MyTask(int num) {
            this.taskNum = num;
        }
        @Override
        public void run() {
            System.out.println("正在执行task " + taskNum);
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task " + taskNum + "执行完毕");
        }
    }

3. 执行线程池

开启线程池,这里通过一个for循环模拟一下,可以看一下log输出,有兴趣的可以修改一下for循环和sleep的数值,看看线程池具体的操作和拒绝流程。

        for (int i = 0; i < 20; i++) {
            MyTask myTask = new MyTask(i);
            threadPoolExecutor.execute(myTask);
            System.out.println("线程池中线程数目:" + threadPoolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" +
                    threadPoolExecutor.getQueue().size() + ",已执行完别的任务数目:" + threadPoolExecutor.getCompletedTaskCount());
        }
        threadPoolExecutor.shutdown();

总结

在此写一些线程操作需要注意的地方:

  • 线程数量和cpu有关,使用线程时一定要注意线程的释放,否则会导致cpu线程数量耗尽;
  • 使用注解完成的线程操作,不可以在自己的类中实现调用,因为注解最后也是通过代理的方式完成异步线程的,最好时在单独的一个service中写;
  • 线程池最好单独写,使用static和final修饰,保证所有使用该线程池的地方使用的是一个线程池,而不能每次都new一个线程池出来,每次都new一个就没有意义了。

以上就是三种线程池的操作,写的不算很详细,有兴趣的同学可以自己在深入研究一下,还有Java8新加的CompletableFuture,可以单独写一篇文章了,在此篇就不再介绍了:)

到此这篇关于java开启异步线程的几种方法(@Async,AsyncManager,线程池)的文章就介绍到这了,更多相关java开启异步线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot使用@EnableAutoConfiguration实现自动配置详解

    SpringBoot使用@EnableAutoConfiguration实现自动配置详解

    你有想过SpringBoot为什么能够自动的帮我们创建一个Bean对象么?或许在我们使用的时候只需要在自己自定义的配置文件中加入@Bean对象就可以,但SpringBoot是如何来创建的呢
    2022-08-08
  • java能写爬虫程序吗

    java能写爬虫程序吗

    在本篇文章里小编给大家整理的是一篇关于java是否能写爬虫程序的一篇文章,对此有兴趣的朋友们可以学习下。
    2021-01-01
  • SpringBoot yaml语法与数据读取操作详解

    SpringBoot yaml语法与数据读取操作详解

    YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言),本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Java InputStream实战之轻松读取操作文件流

    Java InputStream实战之轻松读取操作文件流

    在Java中,输入输出是非常重要的基础功能,其中,InputStream是Java中的一个重要输入流类,用于从输入源读取数据,下面我们就来学习一下InputStream类的相关知识吧
    2023-10-10
  • java 序列化与反序列化的实例详解

    java 序列化与反序列化的实例详解

    这篇文章主要介绍了java 序列化与反序列化的实例详解的相关资料,需要的朋友可以参考下
    2017-07-07
  • Java线程安全解决方案(synchronized,ReentrantLock,Atomic)

    Java线程安全解决方案(synchronized,ReentrantLock,Atomic)

    这篇文章主要介绍了Java线程安全解决方案(synchronized,ReentrantLock,Atomic),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Java 中分形图的几种方法详解

    Java 中分形图的几种方法详解

    这篇文章主要介绍了Java 中几种分形的方法详解的相关资料,需要的朋友可以参考下
    2017-07-07
  • 解决Spring Cloud Feign 请求时附带请求头的问题

    解决Spring Cloud Feign 请求时附带请求头的问题

    这篇文章主要介绍了解决Spring Cloud Feign 请求时附带请求头的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • java.io.File的renameTo方法移动文件失败的解决方案

    java.io.File的renameTo方法移动文件失败的解决方案

    这篇文章主要介绍了java.io.File的renameTo方法移动文件失败的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • MybatisPlus如何自定义TypeHandler映射JSON类型为List

    MybatisPlus如何自定义TypeHandler映射JSON类型为List

    这篇文章主要介绍了MybatisPlus如何自定义TypeHandler映射JSON类型为List,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论