JUC - 线程池:

线程池:

JUC - 线程池:_第1张图片

  1. 为什么使用线程池:(在之前已经是使用过三种创建多线程的方式 那么为什么还要有线程池的方式 一个新技术的出现一定是有它的独到之处 )

    • 线程池的优势:

      • 线程池的优势:线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
    • 线程池的主要特点:

      • 线程复用 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗
      • 控制最大并发数 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行
      • 管理线程 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
    • 线程池的继承关系:Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
      JUC - 线程池:_第2张图片

  2. 使用JDK自带的线程池:使用的类 Executors工具类 但是底层上实际是new ThreadPoolExecutor()

    • 创建一个:有固定线程数的线程池:newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
      JUC - 线程池:_第3张图片

    • 创建一个线程池中只能有一个线程的线程池:newSingleThreadExecutor 创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
      JUC - 线程池:_第4张图片

    • 创建一个可扩容的线程池:newCachedThreadPool创建的线程池将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程
      JUC - 线程池:_第5张图片

    • 虽然是在JDk中的线程池 但是ali开发手册中却是不推荐使用:

      • 前两种线程池使用的是LinkedBlockingQueue 默认创建是Integer.MAX_VALUE个元素 也就是最多能阻塞21亿多个线程 这样就会造成OOM
        JUC - 线程池:_第6张图片
      • 第三种可扩容的最大可扩容的就是21亿个线程 也是同样会造成OOM
  3. 上面的线程池底层都是使用的是ThreadPoolExecutor 这里就详细解释一下:

    • 构造函数:构造函数又非常多种 这一种是最基本的 也就是7个参数的

      public ThreadPoolExecutor(int corePoolSize,  // 线程池中常驻的核心线程数 
                                int maximumPoolSize,  // 线程池中能容纳同时执行的最大线程数 此值必须大于等于1
                                long keepAliveTime,  // 多余的空闲线程的存活时间
                                TimeUnit unit,  // 时间单位
                                BlockingQueue<Runnable> workQueue, // 任务队列 被提交但是还没有执行的任务
                                ThreadFactory threadFactory, // 线程工厂,一般不修改 使用默认的线程工厂来创建线程
                                RejectedExecutionHandler handler // 拒绝策略) {
          if (corePoolSize < 0 ||
              maximumPoolSize <= 0 ||
              maximumPoolSize < corePoolSize ||
              keepAliveTime < 0)
              throw new IllegalArgumentException();
          if (workQueue == null || threadFactory == null || handler == null)
              throw new NullPointerException();
          this.acc = System.getSecurityManager() == null ?
                  null :
                  AccessController.getContext();
          this.corePoolSize = corePoolSize;
          this.maximumPoolSize = maximumPoolSize;
          this.workQueue = workQueue;
          this.keepAliveTime = unit.toNanos(keepAliveTime);
          this.threadFactory = threadFactory;
          this.handler = handler;
      }
      
    • 参数的说明:

      • 拒绝策略:
        • AbortPolicy 丢弃任务并抛出RejectedExecutionException异常 这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态 如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现
          在这里插入图片描述
        • CallerRunsPolicy调用线程处理该任务 从哪来回哪去 这里就是main线程去处理
          JUC - 线程池:_第7张图片
        • DiscardOldestPolicy 丢弃队列最前面的任务,然后重新提交被拒绝的任务。
        • DiscardPolicy 和第一种一样 也是丢弃任务 但是不抛出异常 如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃
      • 线程池中能容纳的最大线程数:maximumPoolSize + 任务队列的容量
    • 自定义线程池的使用:

      package com.interview.concurrent.threadpool;
      
      import java.util.concurrent.*;
      
      public class ThreadPoolExecutorDemo {
      
          public static void main(String[] args) {
              threadPoolExecutor();
      
          }
      
          public static void threadPoolExecutor(){
              ExecutorService threadPool = new ThreadPoolExecutor(
                      2, // 核心池子的大小
                      5, // 线程池最大大小5
                      2L,  // 空闲线程的保留时间
                      TimeUnit.SECONDS, // 超时回收空闲的线程
                      new LinkedBlockingDeque<>(3), // 根据业务设置队列大小,队列大小一定要设置 原因上面说过了
                      Executors.defaultThreadFactory(), // 不用变 一般使用默认的
                      new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略 看这样的创建方式就知道拒绝策略是 ThreadPoolExecutor的静态内部类
              );
              try {
                  // 队列  RejectedExecutionException 拒绝策略
                  for (int i = 1; i <= 10; i++) {
                      // 默认在处理
                      threadPool.execute(()->{
                          System.out.println(Thread.currentThread().getName()+" running....");
                      });
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  threadPool.shutdown();
              }
          }
      }
      
  4. 线程池处理流程:非常重要 (常见的面试题)JUC - 线程池:_第8张图片

    • 处理流程的详解:
      • 在创建了线程池后,线程池中的线程数为零

      • 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

        • 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
        • 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
        • 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
        • 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
      • 当一个线程完成任务时,它会从队列中取下一个任务来执行

      • 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉 线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

        public void execute(Runnable command) {
                if (command == null)
                    throw new NullPointerException();
               
                int c = ctl.get(); // ctl 是一个原子整型
                //addWorker 才是创建线程的方法 也就是任务提交的时候才创建线程
                if (workerCountOf(c) < corePoolSize) {
                    if (addWorker(command, true))
                        return;
                    c = ctl.get();
                }
                // 不是直接创建非核心线程 而是先放在workQueue中
                if (isRunning(c) && workQueue.offer(command)) {
                    int recheck = ctl.get();
                    if (! isRunning(recheck) && remove(command))
                        reject(command);
                    else if (workerCountOf(recheck) == 0)
                        addWorker(null, false);
                }
                else if (!addWorker(command, false))
                    reject(command);
            }
        
  5. 线程池中最大线程数如何配置:

    • CPU 密集型:最大支持多少个线程同时跑,根据CPU去设置,一般设置成与CPU处理器一样大,每一次都要去写吗? 通过Runtime来获取

      Runtime.getRuntime.availableProcessors();// 获取当前的可用核数
      
    • IO 密集型:磁盘读写、 一个线程在IO操作的时候、另外一个线程在CPU中跑,造成CPU空闲。最大线程数应该设置为 IO任务数! 对于大文件的读写非常耗时,我们应该用单独的线程让他慢慢跑

你可能感兴趣的:(JUC,JUC)