1:定义线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableAsync
@Configuration
class
TaskPoolConfig {
@Bean
(
"taskExecutor"
)
public
Executor taskExecutor() {
ThreadPoolTaskExecutor executor =
new
ThreadPoolTaskExecutor();
executor.setCorePoolSize(
10
);
executor.setMaxPoolSize(
20
);
executor.setQueueCapacity(
200
);
executor.setKeepAliveSeconds(
60
);
executor.setThreadNamePrefix(
"taskExecutor-"
);
executor.setRejectedExecutionHandler(
new
ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(
true
);
executor.setAwaitTerminationSeconds(
60
);
1
2
3
return
executor;
}
}
上面我们通过使用ThreadPoolTaskExecutor
创建了一个线程池,同时设置了以下这些参数:
核心线程数10:线程池创建时候初始化的线程数 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程 缓冲队列200:用来缓冲执行任务的队列 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy
策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
说明:setWaitForTasksToCompleteOnShutdown(true)
该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
2:如何使用该线程池呢?
1
2
3
4
5
@Slf4j
@Component
public
class
Task {
public
static
Random random =
new
Random();
@Autowired
private
StringRedisTemplate stringRedisTemplate;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Async
(
"taskExecutor"
)
public
void
doTaskOne()
throws
Exception {
log.info(
"开始做任务一"
);
long
start = System.currentTimeMillis();
Thread.sleep(random.nextInt(
10000
));
long
end = System.currentTimeMillis(); log.info(stringRedisTemplate.randomKey());
log.info(
"完成任务一,耗时:"
+ (end - start) +
"毫秒"
);
}
@Async
(
"taskExecutor"
)
public
void
doTaskTwo()
throws
Exception {
log.info(
"开始做任务二"
);
long
start = System.currentTimeMillis();
Thread.sleep(random.nextInt(
10000
));
long
end = System.currentTimeMillis();
log.info(
"完成任务二,耗时:"
+ (end - start) +
"毫秒"
);
}
@Async
(
"taskExecutor"
)
public
void
doTaskThree()
throws
Exception {
log.info(
"开始做任务三"
);
long
start = System.currentTimeMillis();
Thread.sleep(random.nextInt(
10000
));
long
end = System.currentTimeMillis();
log.info(
"完成任务三,耗时:"
+ (end - start) +
"毫秒"
);
}
}
3 执行异步任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith
(SpringJUnit4ClassRunner.
class
)
@SpringBootTest
public
class
ApplicationTests {
@Autowired
private
Task task;
@Test
public
void
test()
throws
Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
Thread.currentThread().join();
}
}
1
2
3
4
5
6
2018
-
03
-
27
22
:
01
:
15.620
INFO
73703
--- [ taskExecutor-
1
] com.didispace.async.Task : 开始做任务一
2018
-
03
-
27
22
:
01
:
15.620
INFO
73703
--- [ taskExecutor-
2
] com.didispace.async.Task : 开始做任务二
2018
-
03
-
27
22
:
01
:
15.620
INFO
73703
--- [ taskExecutor-
3
] com.didispace.async.Task : 开始做任务三
2018
-
03
-
27
22
:
01
:
18.165
INFO
73703
--- [ taskExecutor-
2
] com.didispace.async.Task : 完成任务二,耗时:
2545
毫秒
2018
-
03
-
27
22
:
01
:
22.149
INFO
73703
--- [ taskExecutor-
3
] com.didispace.async.Task : 完成任务三,耗时:
6529
毫秒
2018
-
03
-
27
22
:
01
:
23.912
INFO
73703
--- [ taskExecutor-
1
] com.didispace.async.Task : 完成任务一,耗时:
8292
毫秒
4 注意事项
注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效
从异常信息JedisConnectionException: Could not get a resource from the pool
来看,我们很容易的可以想到,在应用关闭的时候异步任务还在执行,由于Redis连接池先销毁了,导致异步任务中要访问Redis的操作就报了上面的错。所以,我们得出结论,上面的实现方式在应用关闭的时候是不优雅的,那么我们要怎么做呢?如下设置:
executor.setWaitForTasksToCompleteOnShutdown(
true
);
executor.setAwaitTerminationSeconds(
60
);