JAVA 多线程-newFixedThreadPool()

1,485 阅读6分钟
  • 常用多线程
//创建固定核心数的线程池,这里核心数 = 2
ExecutorService executor02 = Executors.newFixedThreadPool(2);
  • 使用方式及参数配置详解
  1. Executors.newFixedThreadPool(2);
/**
 * 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}
 */
 
 /**
  * 创建一个线程池,重用固定数量的线程操作一个共享的无界队列。在任何时候,最多{@code nThreads}线程将是活动的处理任务。
  * 如果所有线程都处于活动状态时提交了额外的任务,它们将在队列中等待,直到有线程可用。
  * 如果任何线程在执行过程中由于失败而终止在关闭前,如有需要,将会有一个新的取代它的位置执行后续任务。池中的线程将存在
  * 直到显式地{@link ExecutorService#shutdown shutdown}。
  */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
//停止接收新的任务,并继续完成正在执行的任务和队列中的任务
ExecutorService#shutdown

//等所有已提交的任务(包括正在跑的和队列中等待的)执行完 
//或者等超时时间到 
//或者线程被中断,抛出InterruptedException
ExecutorService#awaitTermination(1, TimeUnit.HOURS);

//停止接受新的任务,忽略队列中的任务并尝试终止正在执行的任务
ExecutorService#shutdownNow
  • newFixedThreadPool的特点:
  1. 创建的线程数量固定。
  2. 创建的线程可以重复使用。
  3. 提交一个任务,就创建一个线程,直到达到线程池的最大容量。
  4. 有执行异常结束的线程,线程池会补充一个新的线程。
  5. 使用无边界的队列来存储需要执行的任务。
  6. 自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创建新线程。
  7. 使用完成,需要手动关闭线程池。
  8. 阻塞队列容量大,当队列中推积的数据过多会造成CPU内存过高异常,导致程序异常退出。

简单介绍:

  1. newFixedThreadPool内部有个任务队列,假设线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的。

  2. 如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

问题:

  1. task被提交都了LinkedBlockingQueue中。这里有个问题,如果任务列表很大,一定会把内存撑爆,如何解决?
  2. 可以看出没设置大小,则为Integer.MAX_VALUE
/**
 * Creates a {@code LinkedBlockingQueue} with a capacity of
 * {@link Integer#MAX_VALUE}.
 */
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

期待有好的解决方案,目前解决方案是通过线程池,阿里代码规范:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

public static void main(String[] args) throws IOException, InterruptedException {

   //核心线程数
   int corePoolSize = 3;
   //最大线程数
   int maximumPoolSize = 6;
   //超过 corePoolSize 线程数量的线程最大空闲时间
   long keepAliveTime = 2;
   //以秒为时间单位
   TimeUnit unit = TimeUnit.SECONDS;
   //创建工作队列,用于存放提交的等待执行任务
   BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
   ThreadPoolExecutor threadPoolExecutor = null;
   try {
      //创建线程池
      threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            new ThreadPoolExecutor.AbortPolicy());

      //循环提交任务
      for (int i = 0; i < 8; i++) {
         //提交任务的索引
         final int index = (i + 1);
         threadPoolExecutor.submit(() -> {
            //线程打印输出
            System.out.println("大家好,我是线程:" + index);
            try {
               //模拟线程执行时间,10s
               Thread.sleep(10000);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         });
         //每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序
         Thread.sleep(500);
      }
   } catch (InterruptedException e) {
      e.printStackTrace();
   } finally {
      threadPoolExecutor.shutdown();
   }
}
  • 执行的流程:
1. 首先通过 ThreadPoolExecutor 构造函数创建线程池;

2. 执行 for 循环,提交 8 个任务(恰好等于maximumPoolSize[最大线程数] + capacity[队列大小]);

3. 通过 threadPoolExecutor.submit 提交 Runnable 接口实现的执行任务;

4. 提交第1个任务时,由于当前线程池中正在执行的任务为 0 ,小于 3(corePoolSize 指定),所以会创建一个线程用来执行提交的任务15. 提交第 23 个任务的时候,由于当前线程池中正在执行的任务数量小于等于 3 (corePoolSize 指定),所以会为每一个提交的任务创建一个线程来执行任务;

6. 当提交第4个任务的时候,由于当前正在执行的任务数量为 3 (因为每个线程任务执行时间为10s,所以提交第4个任务的时候,前面3个线程都还在执行中),此时会将第4个任务存放到 workQueue 队列中等待执行;

7. 由于 workQueue 队列的大小为 2 ,所以该队列中也就只能保存 2 个等待执行的任务,所以第5个任务也会保存到任务队列中;

8. 当提交第6个任务的时候,因为当前线程池正在执行的任务数量为3,workQueue 队列中存储的任务数量也满了,这时会判断当前线程池中正在执行的任务的数量是否小于6(maximumPoolSize指定);

9. 如果小于 6 ,那么就会新创建一个线程来执行提交的任务 610. 执行第78个任务的时候,也要判断当前线程池中正在执行的任务数是否小于6(maximumPoolSize指定),如果小于6,那么也会立即新建线程来执行这些提交的任务;

11. 此时,6个任务都已经提交完毕,那 workQueue 队列中的等待 任务4 和 任务5 什么时候执行呢?

12. 当任务1执行完毕后(10s后),执行任务1的线程并没有被销毁掉,而是获取 workQueue 中的任务4来执行;

13. 当任务2执行完毕后,执行任务2的线程也没有被销毁,而是获取 workQueue 中的任务5来执行;  

通过上面流程的分析,也就知道了之前案例的输出结果的原因。其实,线程池中会线程执行完毕后,并不会被立刻销毁,线程池中会保留 corePoolSize 数量的线程,当 workQueue 队列中存在任务或者有新提交任务时,那么会通 过线程池中已有的线程来执行任务,避免了频繁的线程创建与销毁,而大于 corePoolSize 小于等于 maximumPoolSize 创建的线程,则会在空闲指定时间(keepAliveTime)后进行回收。