线程池原理

538 阅读5分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

为什么要用线程池,线程是不是越多越好 ?

  1. 线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间。如果创建时间+销毁时间>执行任务时间就很不合算 。
  2. java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。
  3. 操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
  4. 线程池的推出,就是为了方便的控制线程数量。

线程池原理-概念

  1. 线程池管理器:用于创建并管理线程池,包括创建线程池,销线程池,添加新任务;
  2. 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务
  3. 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定看任务的入口,任务执行完后的收尾工作,任务的执行状态等
  4. 任务队列:用于存放没有处理的任务,提供一种缓冲机制。

线程池API-接口定义和实现类

可以认为ScheduledThreadPoolExecutor是最丰富的实现类

ExecutorService 

ScheduledExecutorService

线程池API-Executors工具类

你可以自己实例化线程,也可以用Executors创建线程池的工厂类,常用方法如下:

newFixedThreadPool(int nThreads) 创建一个固定大小、任务队列容量无界的线程池。核心线程数=最大线程数。

newCachedThreadPool() 创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。适用于执行消耗时较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_VALUE

newSingleThreadScheduledExecutor() 只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务加入的顺序一个一个依次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool的区别在于,单一线程池的池大小在newSingleThreadExecutor()方法中硬编码,不能仔改变的。

newScheduledThreadPool(int corePoolSize) 能定时认知任务的线程池。该池的核心线程数由参数指定,最大线程数=Integer.MAX_VALUE

线程池原理-任务execute过程

  1. 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
  2. 工作队列是否已满?没满,则将新提交的任务存储在工作队列里。
  3. 是否达到线程池最大数量?没达到,则创建一个新的 工作线程来执行任务。
  4. 最后,执行拒绝策略来处理这个任务。

线程数量

如何确定合适数量的线程

计算型任务:cpu数量的1-2倍

IO型任务:相对比计算型任务,需多一些线程,要根据具体的IO阻塞时长进行考量决定。

如tomcat中默认的最大线程数为:200 。

也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。

代码练习

    /**
     * 测试: 提交15个执行时间需要3秒的任务,看线程池的状况
     *
     * @param threadPoolExecutor 传入不同的线程池,看不同的结果
     * @throws Exception
     */
    public void  testCommon(ThreadPoolExecutor threadPoolExecutor) throws InterruptedException {
       for (int i = 0; i < 15; i++) {
           int n = i ;
           threadPoolExecutor.submit(new Runnable() {
               @Override
               public void run() {
                   try {
                       System.out.println("开始执行............"+ n);
                       Thread.sleep(3000L);
                       System.err.println("结束执行............"+ n);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           });
       }
       // 查看线程数量,查看队列等待数量
       Thread.sleep(300L);
       System.out.println("1-当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
       System.out.println("1-当前线程池等待的数量为:" +threadPoolExecutor.getQueue().size());

       // 等待15秒,查看线程数量和队列数量(理论上,会被超出核心线程数量的线程自动销毁)
       Thread.sleep(15000L);
       System.out.println("2-当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
       System.out.println("2-当前线程池等待的数量为:" +threadPoolExecutor.getQueue().size());
    }



示例一
    /**
     * 1、线程池信息: 核心线程数量5,最大数量10,无界队列,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
     *
     * @throws Exception
     */
     public  void threadPoolExecutorTest1() throws InterruptedException {
         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,5, TimeUnit.SECONDS,
                 new LinkedBlockingQueue<Runnable>());
         testCommon(threadPoolExecutor);
         // 无边界队列,核心线程是 5, 最大线程是10  ,因为队列没有边界, 剩余任务都被丢到队列里面了,所以每次执行的就是核心线程数
     }

课后作业

  1. 线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略
  2. 线程池信息: 核心线程数量5,最大数量5,无界队列,超出核心线程数量的线程存活时间:5秒
  3. 核心线程数量0,最大数量Integer.MAX_VALUE,SynchronousQueue队列,超出核心线程数量的线程存活时间:60秒
  4. 定时执行线程池信息:3秒后执行,一次性任务,到点就执行 ,核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
  5. 定时执行线程池信息:线程固定数量5 ,核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
  6. 终止线程:线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
  7. 立刻终止线程:线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的