一、springboot 异步默认线程池
==① 启动类开启异步==
/**
* @PackageName:com.dmo
* @ClassName:App
* @Description: @EnableAsync 开启springboot 异步(线程池)
* @Author:
*/
@SpringBootApplication
@EnableAsync
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
==②Controller 返回值类型为 String,不能用String 接收,而是Future<String>==
@GetMapping("/springpool")
public String SpringPool() throws InterruptedException, ExecutionException {
Future<String> date1 = demoService.TestData1();
Future<String> date2 = demoService.TestData2();
String result = date1.get() + " | " +date2.get() ;
System.out.println(result);
return result;
}
==③实现类Service==
/**
* @PackageName:com.dmo.service
* @ClassName:DemoService
* @Description: 测试springboot 两个异步线程
* @Author:
*/
@Service
public class DemoService {
@Async
public Future<String> TestData1() throws InterruptedException {
//休息3s
TimeUnit.SECONDS.sleep(3);
String result = "线程名:"+Thread.currentThread().getName()+" 当前时间戳:"+System.currentTimeMillis() ;
System.out.println(result);
return new AsyncResult<String>(result);
}
@Async
public Future<String> TestData2() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
String result = "线程名:"+Thread.currentThread().getName()+" 当前时间戳:"+System.currentTimeMillis() ;
System.out.println(result);
return new AsyncResult<String>(result);
}
}
执行结果
二、自定义线程池
==①配置线程池==
Executor 接口是 Java 原生的线程池实现,提供了基本的线程池功能。
/**
* @PackageName:com.dmo.config
* @ClassName:ThreadPoolExecutorConfig
* @Description: 配置线程池
* @Author: john
*/
@Configuration
public class ThreadPoolExecutorConfig {
//核心线程数 = 当前cpu线程数量 +1
//setDaemon 守护线程
private static final int THREADS = Runtime.getRuntime().availableProcessors() + 1;
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("My_Thread -> %d")
.setDaemon(true)
.build();
//线程池名
@Bean("myTaskExecutor")
public Executor myTaskExecutor() {
//7 个参数 =
// 核心线程数,
// 最大线程数,
// 线程存活时间,
// 单位,
// 线程存放阻塞队列,
// 线程创建工厂
// 超出线程的拒绝策略 (4种,默认超出报 Exception )
return new ThreadPoolExecutor(THREADS,
9,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
threadFactory,
(runnable, executor) -> {
// 打印日志,添加监控等
System.out.println("超出最大线程数,已被拒绝!"); });
}
}
或
ThreadPoolTaskExecutor 是 Spring 框架提供的一个类,它提供了更多的扩展功能,比如线程池的前缀、线程池的拒绝策略、线程池的监控等
@Configuration
public class ThreadPoolTaskExecutorConfig {
private static final int corePoolSize = 4; // 核心线程数
private static final int maxPoolSize = 4; // 最大线程数
private static final int keepAliveTime = 10; // 允许线程空闲时间(单位:默认为秒)
private static final int queueCapacity = 15; // 缓冲队列数
private static final String threadNamePrefix = "Thread-PushMessage-Service-"; // 线程池名前缀
@Bean("pushMessageExecutor")
public ThreadPoolTaskExecutor pushMessageExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(threadNamePrefix);
// setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。
executor.setWaitForTasksToCompleteOnShutdown(true);
// setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
// 线程池对拒绝任务的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化
executor.initialize();
return executor;
}
}
② 异步指定线程池
@Async("myTaskExecutor")
七大参数:
1 corePoolSize:线程池核心线程数量,核心线程不会被回收,即使没有任务执行,也会保持空闲状态。如果线程池中的线程少于此数目,则在执行任务时创建。
2 maximumPoolSize:池允许最大的线程数,当线程数量达到corePoolSize,且workQueue队列塞满任务了之后,继续创建线程。
3 keepAliveTime:超过corePoolSize之后的“临时线程”的存活时间。
4 unit:keepAliveTime的单位。
5 workQueue:当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中,BlockingQueue是一个先进先出的阻塞式队列实现,底层实现会涉及Java并发的AQS机制,有关于AQS的相关知识,我会单独写一篇,敬请期待。
6 threadFactory:创建线程的工厂类,通常我们会自顶一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位。
7 handler:线程池执行拒绝策略,当线数量达到maximumPoolSize大小,并且workQueue也已经塞满了任务的情况下,线程池会调用handler拒绝策略来处理请求。
四大拒绝策略:
1 AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
2 DiscardPolicy:直接抛弃不处理。
3 DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务
4 CallerRunsPolicy:使用当前调用的线程来执行此任务
例。。。。。。。。。。。。。。。。。。。。。。。
多线程调用 service,结果汇总,节约时间
controller
@Autowired
private Test test;
@RequestMapping("/test1/find3")
public String find23() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(2);
//线程1执行service 的 a方法
Future<Integer> a = test.a(countDownLatch);
//线程2执行service 的 b方法
Future<Integer> b = test.b(countDownLatch);
//阻塞等待线程全部执行完毕
countDownLatch.await();
System.out.println(a.get());
System.out.println(b.get());
//汇总每个线程的调用结果
System.out.println("a+b="+(a.get()+b.get()));
long cost = System.currentTimeMillis() - start;
String logStr = String.format("查询耗时: %s ms", cost);
log.info(logStr);
return logStr;
}
service
@Service
public class Test {
@Async()
public Future<Integer> a(CountDownLatch countDownLatch) throws InterruptedException {
System.out.println("a");
countDownLatch.countDown();
Thread.sleep(2000);
System.out.println("a结束时间:"+System.currentTimeMillis());
return new AsyncResult<Integer>(1);
}
@Async()
public Future<Integer> b(CountDownLatch countDownLatch) throws InterruptedException {
System.out.println("b");
countDownLatch.countDown();
Thread.sleep(2000);
System.out.println("b结束时间:"+System.currentTimeMillis());
return new AsyncResult<Integer>(2);
}
}
在service中主动使用线程池!!!
注意,核心线程数此处设置为4 ,程序始终会占用4个核心线程不释放。
@Resource
@Qualifier("pushMessageExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void executeAsyncTask() {
taskExecutor.execute(() -> {
// 任务代码
});
}
如果想临时使用多个线程,之后再释放全部线程,这样写
@RequestMapping("/hello100")
public void sayHello100() throws InterruptedException {
// 10s 后释放线程
// 默认创建9个核心线程,若任务超过9个,则加入队列由核心线程处理,若队列也满了,则创建新线程处理,队列里的仍然由核心线程处理,线程最多不能超过50个,新创建的线程会等10s,10s没有任务,则销毁新创建的线程,
// 队列满了才会才能创建新线程,否则一直使用核心线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(9, 50, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1));
executor.allowCoreThreadTimeOut(true); // 核心线程若 10s没任务 也会销毁。
for (int i = 0; i < 30; i++) {
executor.execute( new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "处理任务ing...");
}
});
}
}
***************** 建议使用异步线程:@Async 如下实例 **********************
例:
@SpringBootApplication
@EnableAsync
public class SpringbootjpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootjpaApplication.class, args);
}
}
线程池:
@Configuration
public class ThreadPoolTaskExecutorConfig {
private static final int corePoolSize = 4; // 核心线程数
private static final int maxPoolSize = 4; // 最大线程数
private static final int keepAliveTime = 10; // 允许线程空闲时间(单位:默认为秒)
private static final int queueCapacity = 15; // 缓冲队列数
private static final String threadNamePrefix = "MyThreadPool-Service-"; // 线程池名前缀
@Bean("MyThreadPool")
public ThreadPoolTaskExecutor pushMessageExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(threadNamePrefix);
// setWaitForTasksToCompleteOnShutdown(true): 该方法用来设置 线程池关闭 的时候 等待 所有任务都完成后,再继续 销毁 其他的 Bean,这样这些 异步任务 的 销毁 就会先于 数据库连接池对象 的销毁。
executor.setWaitForTasksToCompleteOnShutdown(true);
// setAwaitTerminationSeconds(60): 该方法用来设置线程池中 任务的等待时间,如果超过这个时间还没有销毁就 强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
// 线程池对拒绝任务的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化
executor.initialize();
return executor;
}
}
controller
@RequestMapping("pool")
public void pool() {
userService.testpool();
}
serviceimpl
@Service
public class UserServiceImpl implements UserService {
//一定要加这个
@Autowired
@Lazy
private UserServiceImpl userServiceimpl;
@Override
public void testpool() {
System.out.println("主线程:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
userServiceimpl.pool0();
userServiceimpl.pool1();
System.out.println("主线程:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
@Async("MyThreadPool")
public void pool0() {
System.out.println("线程池:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
try { Thread.sleep(5000L);}catch (Exception e){}
System.out.println("线程池:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
@Async("MyThreadPool")
public void pool1() {
System.out.println("线程池:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
try { Thread.sleep(5000L);}catch (Exception e){}
System.out.println("线程池:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
}
结果:主线程和线程池全是异步
============================================ 另一个例子 ============================================
@Component
public class TestTask {
@Async("MyThreadPool")
public void task1(){
System.out.println("task1线程:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task1线程:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
@Async("MyThreadPool")
public Future<String> task2(){
System.out.println("task2线程:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回当前线程的线程名称
System.out.println("task2线程:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
return new AsyncResult<>(Thread.currentThread().getName());
}
}
@Autowired
private TestTask testTask;
@RequestMapping("test2")
public String test2() {
System.out.println("主线程:"+Thread.currentThread().getName()+"---start--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
//异步获取子线程2返回数据
Future<String> res = testTask.task2();
//子线程1 无返回数据
testTask.task1();
// 主线程等子线程5s,5秒还没返回数据,主线程直接返回。
for (int i = 5; i > 0; i--) {
// 已完成
if(res.isDone()){
System.out.println("task2任务已经完成");
try { System.out.println("task2任务已经完成,返回值为:" + res.get()); }catch (Exception e){ System.out.println("获取异步内容失败!"); }
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace();}
}
System.out.println("主线程:"+Thread.currentThread().getName()+"---end--- "+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
if(res.isDone()){
return "success";
}
return "fail";
}