浅谈java开启异步线程的几种方法(@Async,AsyncManager,线程池)
Posted 天河归来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈java开启异步线程的几种方法(@Async,AsyncManager,线程池)相关的知识,希望对你有一定的参考价值。
浅谈java开启异步线程的几种方法
整体描述
在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查看结果:
接口直接返回了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异步调用的几种方式
一、通过创建新线程【Java资料领取】
首先的我们得认识到,**异步调用的本质,其实是通过开启一个新的线程来执行。**如以下例子:
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
new Thread(() -> {
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}).start();
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
数据结果如下所示,我们知道,System.currentTimeMillis()
时间单位为ms。
主线程 =====> 开始 =====> 1627893837146
异步线程 =====> 开始 =====> 1627893837200
主线程 =====> 结束 =====> 1627893839205
异步线程 =====> 结束 =====> 1627893842212
我们通过线程休眠来达成主线程执行时间2秒左右,异步线程执行5秒左右的效果。通过打印出来的时间戳倒数第四位(秒位)我们可以看出,两个的线程执行总时间为5秒左右,符合异步执行的特征
以上是采用Runable实现多线程创建方式的lambda写法,关于的lambda知识,可参考Java Lambda 表达式;而关于多线程的多种实现方式,Java多线程事务管理一文有提及,可移步查看
二、通过线程池
因为异步任务的实现本质的由新线程来执行任务,所以通过线程池的也可以实现异步执行。写法同我们利用线程池开启多线程一样。但由于我们的目的不是执行多线程,而是异步执行任务,所以一般需要另外一个线程就够了。
因此区别于执行多线程任务的我们常用的newFixedThreadPool
,在执行异步任务时,我们用newSingleThreadExecutor
来创建一个单个线程的线程池。
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(()->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
});
executorService.shutdown(); // 回收线程池
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
执行结果如下:
主线程 =====> 开始 =====> 1627895467578
异步线程 =====> 开始 =====> 1627895467635
主线程 =====> 结束 =====> 1627895469644
异步线程 =====> 结束 =====> 1627895472649
可以看到,结果跟第一种结果是基本一致的。
温馨提示:不要忘记线程池的回收
三、通过@Async注解
我们都知道,SpringBoot项目有一个的很重要的特点就是的注解化。如果你的项目是SpringBoot,那就又多了一种选择——@Async注解。
使用起来也非常简单,将要异步执行的代码封装成一个方法,然后用@Async注解该方法,然后在主方法中直接调用就行。
@Test
public void mainThread() throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
collectionBill.asyncThread();
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
Thread.sleep(4000); // 用于防止jvm停止,导致异步线程中断
}
@Async
public void asyncThread(){
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}
执行结果如下:
主线程 =====> 开始 =====> 1627897539948
异步线程 =====> 开始 =====> 1627897539956
主线程 =====> 结束 =====> 1627897541965
异步线程 =====> 结束 =====> 1627897544966
有以下两点需要注意:
- 类似
@Tranctional
注解,@Async
注解的方法与调用方法不能在同一个类中,否则不生效- JUnit框架的设计不考虑多线程场景,所以主线程退出后,子线程也会跟着立即退出,所以可以在后面加多线程休眠时间来观察异步线程的执行情况
四、通过CompletableFuture
CompletableFuture是JDK1.8的新特性,是对Future的扩展。CompletableFuture实现了CompletionStage接口和Future接口,增加了异步回调、流式处理、多个Future组合处理的能力。
实现代码如下:
public static void main(String[] args) throws Exception{
System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture.runAsync(() ->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
},executorService);
executorService.shutdown(); // 回收线程池
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis());
}
同样可以实现类似的结果如下:
主线程 =====> 开始 =====> 1627898354914
异步线程 =====> 开始 =====> 1627898354977
主线程 =====> 结束 =====> 1627898356980
异步线程 =====> 结束 =====> 1627898359979
2021年最新阿里P7 Java面试题流出,附完整答案详解,看完跳槽吊打面试官
-End-
最近有一些小伙伴,让我帮忙找一些Java面试资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是Java面试必备!所有资料都整理到网盘了,欢迎下载!
需要的小伙伴,可以扫码领取
以上是关于浅谈java开启异步线程的几种方法(@Async,AsyncManager,线程池)的主要内容,如果未能解决你的问题,请参考以下文章