文章目录
- 整体描述
- 实现方法
- 一、注解@Async
- 1. 添加注解
- 2. 创建异步方法Service和实现类
- 3. 调用异步方法
- 二、AsyncManager
- 1. 创建AsyncManager类
- 2. 创建一个耗时的操作类
- 3. 执行异步操作
- 三、线程池
- 1. 创建线程池
- 2. 创建一个耗时的操作类
- 3. 执行线程池
- 总结
整体描述
在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秒的操作*/@Asyncpublic 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);@Overridepublic 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);@Autowiredprivate 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() {@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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一个就没有意义了。
以上就是三种线程池的操作,写的不算很详细,有兴趣的同学可以自己在深入研究一下。