Java ExecutorService 线程池使用记录

1.为什么要用线程池,以及线程池的好处是什么?

           在日常开发中,为了提高处理效率,以及硬件资源的利用率,我们经常使用多线程技术。如果我们每次都频繁创建新线程、销毁新线程,这样操作不可取。

        原因:创建线程、销毁线程,线程切换,消耗大量的系统资源,影响处理效率。且有时服务器无法处理过多请求导致崩溃。(创建线程以及销毁线程远远大于线程执行时间)。这时候,主角该出场了,他就是线程池:ExecutorService。

使用线程池的好处:

1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2.什么是线程池?

ExecutorService接口 继承于 父类Executor

 2.1 ExecutorService  核心方法(可自定义线程池)

类之间的关系

1. ThreadPoolExecutor extends AbstractExecutorService
2. AbstractExecutorService implements ExecutorService
3. ExecutorService extends Executor

解释:

ThreadPoolExecutor(该类就是线程池类)继承AbstractExecutorService类,该抽象类实现ExecutorService接口,Executors是一个工厂类,包含很多静态方法,包括newCachedThreadPool、newSingleThreadExecutor、newFixedThreadPool等,这些静态方法中调用了ThreadPoolExecutor的构造函数,并且不同的线程池调用构造方法时传入不同的参数。

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

参数说明:

corePoolSize : 核心线程数,线程池维护的最小线程数量

核心线程池大小,活动线程小于corePoolSize则直接创建,大于等于则先加到workQueue中,队列满了才创建新的线程。

maximumPoolSize : 最大线程数,允许创建的最大线程数量。如果最大线程数 > 核心线程数时,核心线程都处于忙碌状态,则自动创建非核心线程,直到线程数总数 = 最大线程数。如果最大线程数 = 核心线程数,则无法创建非核心线程。如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。

非核心线程数超过最大线程数,就reject;线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

keepAliveTime : 当非核心当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁。线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

unit : 时间单位,线程池维护非核心线程的空闲时间单位。可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、  毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。

workQueue : 任务队列,用于保存等待执行的任务的阻塞队列。线程池中的工作线程都是从这个工作队列源源不断的获取任务进行执行。

分为以下几个类型:

  1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,必须设置容量。此队列按 FIFO(先进先出)原则对元素进行排序。
  2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,可以设置容量(默认大小:Integer.MAX_VALUE),此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入offer操作必须等到另一个线程调用移除poll操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  4. PriorityBlockingQueue:一个具有优先级的无限阻塞队列

threadFactory :  线程工厂,用于创建线程。

handler : 拒绝策略,当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序。

四种拒绝策略:

序号拒绝策略类型说明
1ThreadPoolExecutor.AbortPolicy

默认拒绝策略

拒绝任务并抛出任务

2ThreadPoolExecutor.CallerRunsPolicy谁提交任务谁来执行这个任务,即将任务执行放在提交的线程里面,减缓了线程的提交速度,相当于负反馈。在提交任务线程执行任务期间,线程池又可以执行完部分任务,从而腾出空间来。
3ThreadPoolExecutor.DiscardPolicy直接拒绝任务,不抛出错误
4ThreadPoolExecutor.DiscardOldestPolicy

触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入。队列中头节点的任务无情的被抛弃。

3.ExecutorService的类型

四种线程池:

  1. FixedThreadPool固定线程数线程池
  2. SingleThreadExecutor单线程池
  3. CachedThreadPool可缓存线程池
  4. ScheduledThreadPool 固定线程数,支持定时和周期性任务线程池

1.FixedThreadPool固定线程数线程池

/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

参数说明:

coresize = nThreads、maxmumsize = nThreads,非核心线程超时时间为0,队列用的LinkedBlockingQueue无界的FIFO队列,如果队列里面有线程任务的话就从队列里面取出线程,然后开启一个新的线程开始执行。 很明显,这个线程池始终只有固定的nThreads的线程在运行,大小固定,难以扩展。

2.SingleThreadExecutor单线程池

 /**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

参数说明:

只用一个线程来执行任务,LinkedBlockingQueue无界的FIFO队列顺序一个个执行。

3.CachedThreadPool可缓存线程池

/**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

参数说明:

        上述方法可以看到,创建的都是非核心线程,而且最大线程数为Interge的最大值,空闲线程存活时间是1分钟。SynchronousQueue队列,一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作。所以,当我们提交第一个任务的时候,是加入不了队列的,这就满足了,一个线程池条件“当无法加入队列的时候,且任务没有达到maxsize时,我们将新开启一个线程任务”。即当线程不够用的时候会不断创建新线程,如果线程无限增长,会导致内存溢出。所以我们的maxsize是big big。时间是60s,当一个线程没有任务执行会暂时保存60s超时时间,如果没有的新的任务的话,会从cache中remove掉。因此长时间不提交任务的CachedThreadPool不会占用系统资源。就是缓冲区为1的生产者消费者模式。

4.ScheduledThreadPool 固定线程数,支持定时和周期性任务线程池

 /**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

周期性或延迟、定时执行执行线程。

------------------------------------------------------------------------------------------------------------------------

了解完概念,接下来就开始放大招了,怎么使用呢

-----------------------------------------------------------------------------------------------------------------------

4.使用线程池

以使用newFixedThreadPool 示例:
提交同步任务有2中方式submit()  和 execute()

区别:

submit():可以接受runnable和callable两种参数,并且有返回值
execute()只能接受runnable, 无返回值

1.execute()

 public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

2.submit()

     public static void main(String[] args) throws InterruptedException {
 4         //创建一个容量为8的线程池
 5         ExecutorService executorService = Executors.newFixedThreadPool(8);
 6         for(int i = 0;i<100;i++){
 7             //向线程池提交一个任务
                 ExcuteAddataCheck checkMethord = new ExcuteAddataCheck(batchId, str);
 8             executorService.submit(checkMethord);
 9        System.out.println("------------" + i + "-------------");
10        Thread.sleep(5000); 
         System.out.printlin("主线程休眠了5秒钟")
11         }
12     }
13 }

异步类

@Slf4j
public class ExcuteAddataCheck implements Runnable {
 private Integer batchId;
 private String value;

 public ExcuteAddataCheck(Integer batchId, String value) {
        this.batchId = batchId;
        this.value = value;
}


  @Override
    public void run() {
        try {
            KafkaUtil.repairSender(value, batchId);
        } catch (Exception e) {
          log.error("batchId:{} 异常, Exception", batchId, e);
        } finally {
            log.info("结束,finally");
        }
    }

}

5.怎么关闭线程池?

shutdown() :线程池不再接受新任务,会等待其他线程都执行完毕之后再关闭。

shutdownNow() :线程池立即停止接受新任务,对未开始的任务全部取消,则相当于调用每个线程的 interrupt() 方法,返回为开始执行的任务列表。

6.线程池核心线程数设置规则

一般经验:

CPU密集型:核心线程数 = CPU核数 + 1

IO密集型:核心线程数 = CPU核数 * (2~4)

7.线程池的监控

        服务中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数。
  • ThreadPoolExecutor.getQueue().size():等待队列中的任务数量

        通过扩展线程池进行监控,可以通过继承线程池来自定义线程池,重写线程池的钩子方法,例如:beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

8. 系统提供4种线程的使用注意点

线程池队列注意点
FixedThreadPoolLinkedBlockingQueue等待队列:
Integer.MAX_VALUE
SingleThreadExecutorLinkedBlockingQueue等待队列:
Integer.MAX_VALUE
CachedThreadPoolSynchronousQueue
maximumPoolSize:Integer.MAX_VALUE
ScheduledThreadPoolDelayedWorkQueuemaximumPoolSize:Integer.MAX_VALUE

-----------------------------------------------------------------------------------------------------------------------------

无论正在经历什么,都请不要轻言放弃,因为从来没有一种坚持会被辜负。谁的人生不是荆棘前行,生活从来不会一蹴而就,也不会永远安稳,只要努力,就能做独一无二平凡可贵的自己!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用线程池多线程分页插入数据的 Java 代码示例: 首先,我们需要定义一个线程池,并设置最大线程数为 10: ``` ExecutorService executorService = Executors.newFixedThreadPool(10); ``` 接下来,我们需要获取总记录数,以及每页的记录数和总页数: ``` int totalNum = getTotalNum(); // 获取总记录数 int pageSize = 100; // 每页的记录数 int pageNum = totalNum % pageSize == 0 ? totalNum / pageSize : totalNum / pageSize + 1; // 总页数 ``` 然后,我们可以使用循环来遍历每一页,并提交任务给线程池: ``` for (int i = 1; i <= pageNum; i++) { final int currentPage = i; executorService.submit(new Runnable() { @Override public void run() { List<Data> dataList = getDataList(currentPage, pageSize); // 获取当前页的数据列表 insertDataList(dataList); // 插入数据 } }); } ``` 在任务的 run 方法中,我们首先需要获取当前页的数据列表,然后调用插入数据的方法来插入数据: ``` public List<Data> getDataList(int currentPage, int pageSize) { // 根据当前页和每页的记录数查询数据 // ... } public void insertDataList(List<Data> dataList) { // 插入数据 // ... } ``` 最后,我们需要关闭线程池: ``` executorService.shutdown(); ``` 完整的代码示例: ``` ExecutorService executorService = Executors.newFixedThreadPool(10); int totalNum = getTotalNum(); // 获取总记录数 int pageSize = 100; // 每页的记录数 int pageNum = totalNum % pageSize == 0 ? totalNum / pageSize : totalNum / pageSize + 1; // 总页数 for (int i = 1; i <= pageNum; i++) { final int currentPage = i; executorService.submit(new Runnable() { @Override public void run() { List<Data> dataList = getDataList(currentPage, pageSize); // 获取当前页的数据列表 insertDataList(dataList); // 插入数据 } }); } executorService.shutdown(); ``` 需要注意的是,该示例仅是一个简单的示例,实际使用中还需要考虑一些细节问题,比如异常处理、分页查询的 SQL 语句等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值