java多线程之ExecutorService的使用方法
spring管理多线程
@Autowired
private ThreadPoolTaskExecutor executor;
executor.execute(() -> {
try {
//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
});
注意,开启bean配置,可以修改相关参数
@Configuration
public class ThreadConfig {
@Bean
public ThreadPoolTaskExecutor threadPool() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
threadPool.setCorePoolSize(10);
threadPool.setMaxPoolSize(50);
threadPool.setQueueCapacity(10000);
threadPool.setKeepAliveSeconds(300);
threadPool.initialize();
return threadPool;
}
}
ExecutorService详解
ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。
1、创建方式:
ExecutorService executorService = new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler);
corePoolSize : 核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。
maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
通俗解释,如果把线程池比作一个单位的话,corePoolSize就表示正式工,线程就可以表示一个员工。当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是干不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是workQueue(如果workQueue是无界的,那么相当于可以一直堆积任务,则maximumPoolSize等于是无效的,所以我们在构建线程池时不能使用无界队列!!),等正式工完成了手上的工作,就到这里来取新的任务。如果不巧,年末了,各个部门都向这个单位委派任务,导致workQueue已经没有空位置放新的任务,于是单位决定招点临时工吧。临时工也不是想招多少就找多少,上级部门通过这个单位的maximumPoolSize确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize–corePoolSize个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。
keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。
unit : 时间单位,TimeUnit.SECONDS等。
workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。
threadFactory : 线程工程,用于创建线程。
handler : 当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序
踩坑记录
无界队列的使用误区:
什么是无界队列?指的是没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出。当然现实几乎不会有到这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的体验上,就相当于 “无界”。比如没有设定固定大小的 LinkedBlockingQueue。所以无界队列的特点就是可以一直入列,不存在队列满负荷的现象。
多线程创建流程:当核心线程数满了后,任务优先进入等待队列。如果等待队列也满了后,才会去创建新的非核心线程 。如果我们设计的线程池,使用了无界队列,会直接导致最大线程数的配置失效;很容易造成内存溢出,所以我们应当使用有界队列!!!
手动创建线程池
这种方式创建的线程池,idea不会有如下警告提示:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
int size = 8;
//切记使用有界队列new LinkedBlockingQueue<Runnable>(128)
ExecutorService executorService = new ThreadPoolExecutor(size, size, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(128), namedThreadFactory);
ExecutorService.execute和ExecutorService.submit的区别
1、接收的参数不一样
2、submit有返回值,而execute没有
3、submit方便Exception处理
意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。
/**
* execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
*/
executorService.execute(new RunnableTest("Task1"));
/**
* submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
*/
Future future = executorService.submit(new RunnableTest("Task2"));
try {
if(future.get()==null){//如果Future's get返回null,任务完成
System.out.println("任务完成");
}
} catch (InterruptedException e) {
} catch (ExecutionException e) {
//否则我们可以看看任务失败的原因是什么
System.out.println(e.getCause().getMessage());
}
CyclicBarrier的工作原理及其实例
CyclicBarrier大致是可循环利用的屏障,顾名思义,这个名字也将这个类的特点给明确地表示出来了。首先,便是可重复利用,说明该类创建的对象可以复用;其次,屏障则体现了该类的原理:每个线程执行时,都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。
CyclicBarrier的两个构造函数:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。
注意:
CyclicBarrier实际是使多线程操作完后直接被销毁了,如果在CyclicBarrier函数里再调用HttpServletResponse输出流操作,则会出现空指针异常,所有如果有调用输出流的操作不应该使用CyclicBarrier可以使用CountDownLatch。
CyclicBarrier barrier = new CyclicBarrier(size, () -> {
//这里汇总数据(如果多线程有返回值则在这汇总)
System.out.println("预警管理多线程处理业务完毕!");
});
具体案例代码
//查询某个表的数据
List<SysWarningManage> sysWarningManages = quaWarningManageService.queryList(param);
//手动创建线程池
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
int size = sysWarningManages.size();
ExecutorService executorService = new ThreadPoolExecutor(size, size, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(128), namedThreadFactory);
CyclicBarrier barrier = new CyclicBarrier(size, () -> {
//这里汇总数据(如果多线程有返回值则在这汇总)
System.out.println("预警管理多线程处理业务完毕!");
});
for (SysWarningManage sysWarningManage : sysWarningManages) {
//多线程执行循环
executorService.execute(() -> {
//这里写业务逻辑
if(sysWarningManage.getId()!=null){
}else{
}
try {
//执行线程阻塞(拦截线程)
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
CountDownLatch
CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下,CountDownLatch能够使一个或多个线程等待其他线程完成各自的工作后再执行;
CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。
public void exportOrderCallbackList(HttpServletResponse response, OrderCallbackQueryDTO query){
Assert.notNull(query, "OrderCallbackServiceImpl exportOrderCallbackList param query can not be null");
try {
List<OrderCallbackExcelDTO> orderCallbackExcelDTOs = orderCallbackDao.exportOrderCallbackList(query);
//CopyOnWriteArrayList线程安全
List<OrderCallbackExportDTO> list = new CopyOnWriteArrayList<OrderCallbackExportDTO>();
//开启多线程查询
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-exportOrderCallbackList-%d").build();
int size = orderCallbackExcelDTOs.size();
int corePoolSize = 1;
if(size <= 0 ){
return;
}else if(size <= 100){
corePoolSize = size/2;
}else if(size > 100){
corePoolSize = 80;
}
ExecutorService executorService = new ThreadPoolExecutor(corePoolSize, size, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(128), namedThreadFactory);
CountDownLatch workLauch = new CountDownLatch(size);
for(OrderCallbackExcelDTO or:orderCallbackExcelDTOs){
executorService.execute(() -> {
try {
if(or != null){
//具体业务逻辑
OrderCallbackExportDTO oceDTO = new OrderCallbackExportDTO();
oceDTO.setId(or.getId());
oceDTO.setOrderNo(or.getOrderNo());
oceDTO.setCusId(or.getCusId());
oceDTO.setCusName(or.getCusName());
list.add(oceDTO);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 很关键, 无论上面程序是否异常必须执行countDown,否则await无法释放
workLauch.countDown();
}
});
}
try {
//所有个线程countDown()都执行之后才会释放当前线程,程序才能继续往后执行
workLauch.await();
//多线程查询完后按日期进行倒序
Collections.sort(list,new Comparator<OrderCallbackExportDTO>(){
@Override
public int compare(OrderCallbackExportDTO o1, OrderCallbackExportDTO o2) {
//升序排的话就是第一个参数.compareTo(第二个参数);降序排的话就是第二个参数.compareTo(第一个参数);
if(o2.getPayTime()!=null && o1.getPayTime()!= null){
return o2.getPayTime().compareTo(o1.getPayTime());
}else{
return 1;
}
}
});
LinkedHashMap<String, String> fieldMap = new LinkedHashMap<>();
fieldMap.put("id", "序号");
fieldMap.put("orderNo", "支付订单号");
fieldMap.put("cusId", "客户ID");
fieldMap.put("cusName", "客户姓名");
log.info("list.size----[{}]",list.size());
try {
//导出Excel接口
ListToExcelUtil.listToExcel(list, fieldMap, FILE_NNAME + new SimpleDateFormat(DATE_FORMATE).format(new Date()), SHEET_NAME, list.size(), response);
} catch (Exception e) {
e.printStackTrace();
}
log.info("导出Excel多线程处理业务完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("executorService----[{}]","关闭线程池");
executorService.shutdown();
} catch (Exception e) {
log.info("DispatchCorpLogServiceImpl exportOrderCallbackList failed error", e);
}
}