Java线程池创建和实际项目详解

37 阅读4分钟

一、线程池概述

线程池提供了一个线程队列,队列中保存着所有等待状态的线程。通过避免频繁创建与销毁线程带来的额外开销,线程池能够显著提高系统的响应速度。本文将详细介绍一下线程池相关概念及如何使用线程池,欢迎大家评论指点。

二、线程池的体系结构

首先先介绍一下线程池的体系结构,主要有如下接口类。

  1. java.util.concurrent.Executor:负责线程的使用与调度的根接口。
  2. ExecutorService 子接口:线程池的主要接口。
  3. ThreadPoolExecutor:线程池的实现类。
  4. ScheduledExecutorService 子接口。
  5. ScheduledThreadPoolExecutor:继承自 ThreadPoolExecutor,实现了 ScheduledExecutorService 接口。

三、工具类:Executors

首先创建线程池的第一种方式是使用工具类Executors,主要如下几种方式:

  1. Executors.newFixedThreadPool():创建固定大小的线程池。
  2. Executors.newCachedThreadPool():创建缓存线程池,线程数量不固定,可以根据需求自动调整。
  3. Executors.newSingleThreadExecutor():创建单个线程池,线程池中只有一个线程。
  4. Executors.newSingleThreadScheduledExecutor():创建固定大小的线程,可以延迟或定时执行任务。

四、使用 ThreadPoolExecutor 自定义创建多线程

接下来就开始自定义创建多线程,主要是通过 ThreadPoolExecutor 的构造函数,可以自定义创建多线程。构造函数包括5个基本参数和2个可选参数:

  1. corePoolSize:核心池的大小,即线程池维护线程的最少数量。
  2. maximumPoolSize:线程池中线程的最大数量。
  3. keepAliveTime:线程池维护线程所允许的空闲时间。
  4. unit:线程池维护线程所允许的空闲时间的单位。
  5. workQueue:线程池所使用的缓冲队列。
  6. handler:线程池对拒绝任务的处理策略(可选参数)。
  7. threadFactory:自定义线程工厂(可选参数),可以使用 com.google.guava 的 ThreadFactoryBuilder 来指定线程名称。 创建代码如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        4,
        4,
        0L,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingDeque<>(10),
        threadFactory,// 线程工厂可以不用传参
        new ThreadPoolExecutor.CallerRunsPolicy()//线程拒绝策略,也可以不用传参
);

五、线程池 ThreadPoolExecutor 中的 execute 和 submit 方法对比

  1. 定义方法的类不同:submit 在 ExecutorService 接口中定义,而 execute 方法在 Executor 类中定义,ThreadPoolExecutor 实现了这两个接口。
  2. 返回值类型不同:execute 方法返回值为空,而 submit 方法会以 Future 的形式返回线程的执行结果。
  3. 对异常的处理方式不同:execute 方法会直接打印产生的异常堆栈,而 submit 方法提交的子线程产生异常时,调用 Future 实例的 get 方法可以在主线程捕获异常。

execute接口方法

public interface Executor {     void execute(Runnable command); }

submit方法

Future submit(Runnable task); 
Future submit(Runnable task, T result); 
Future submit(Callable task);

六、项目应用(使用场景)

  1. 并发量大的场景:如批量查询,通过多线程(线程池)分配处理任务,提高系统性能。 比如下面案例代码:

//List分批处理
    List> productList = Lists.partition(activityProductBatchSaveDTO.getProductIds(), 15);
    long time = System.currentTimeMillis();
    // 
    Map productFullsById = new ConcurrentHashMap<>();
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(productList.size(),productList.size(),1, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100000),
            new ThreadFactoryBuilder().setNameFormat("OMG-Tool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    CountDownLatch countDownLatch =new CountDownLatch(productList.size());
productList.forEach(productIds->{
        threadPoolExecutor.execute(()->{
            ProductIdRequest request = BeanMapper.map(req, ProductIdRequest.class);
            request.setProductIds(productIds);
            productFullsById.putAll(productFacade.getProductsByIdsV3(request));
            countDownLatch.countDown();
        });
    });
try {
        countDownLatch.await();
    }catch (Exception e){
    }finally {
        threadPoolExecutor.shutdown();
    }

  1. 耗时较长的任务:如用户注册后发送邮件、短信等操作,可以使用异步线程处理,即使任务失败影响也不大。
  2. 定时任务:如定期更新配置文件、备份数据等任务,可以使用定时线程池执行。

总结

总的来说,利用线程池可以动态配置线程池相关参数,包括核心线程池和非核心线程池。当内存紧张时,可以释放部分线程或关闭非核心线程池。线程池具有以下优点:

  1. 降低资源销毁:重用存在的线程,减少对象创建销毁的开销。
  2. 提高线程管理性:有效控制最大并发线程数、提高系统资源的使用率,避免过多资源竞争和阻塞。
  3. 提高响应速度:任务到达时,无需创建线程即可执行。提供定时执行、定期执行、单线程、并发数控制等功能。