通俗易懂理解线程池【01】

1. 线程池

1.0 为什么使用线程池

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 什么是任务?肯定是一段包含在方法中的代码,那么任务是方法吗?不是,在java中,不能传递方法,方法不能放在队列似的容器中,所以任务实际上是一个对象,这个对象中有一个方法,这个方法中就是要执行的任务。

1.1 ThreadPoolExecutor类

  • java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。

  • ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,前三个构造器都是调用了最后一个构造器,第四个构造器如下:

    public ThreadPoolExecutor(
        int corePoolSize,                     // 核心线程数
        int maximumPoolSize,                  // 最大线程数
        long keepAliveTime,                   // 表示线程没有任务执行时最多保持多久时间会终止
        TimeUnit unit,                        // keepAliveTime的时间单位
        BlockingQueue<Runnable> workQueue,    // 阻塞队列
        ThreadFactory threadFactory,          // 用来创建线程的线程工厂
        RejectedExecutionHandler handler      // 拒绝策略
    );
    
  • 构造器中各参数含义

    • corePoolSize:核心线程数。创建线程池后,默认情况下线程池中没有任何线程,而是等到有任务到来才创建线程执行任务。除非调用下面两个方法:

      • prestartAllCoreThreads():在任务到来之前就创建corePoolSize个线程。(预创建所有的核心线程)
      • prestartCoreThread():在任务到来之前就创建1个线程。(预创建一个核心线程)
      • 默认情况下,创建线程池后线程池中的线程数为0,当有任务到来时就会创建一个线程去执行任务,当线程池中的线程数达到corePoolSize后,就会把到达的任务放到阻塞队列中。
    • maximumPoolSize:线程池的最大线程数。表示在线程池中最多创建几个线程。

    • keepAliveTime:线程没有任务执行时最多保持多久会终止。

      • 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用(即默认情况下该参数只针对于非核心线程),直到线程池中的线程数不大于corePoolSize。即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
      • 如果调用了allowCoreThreadTimeOut(boolean)方法,那么在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
    • unit:keepAliveTime的单位。七种。

      TimeUnit.DAYS;              // 天
      TimeUnit.HOURS;             // 小时
      TimeUnit.MINUTES;           // 分钟
      TimeUnit.SECONDS;           // 秒
      TimeUnit.MILLISECONDS;      // 毫秒
      TimeUnit.MICROSECONDS;      // 微秒。即千分之一毫秒
      TimeUnit.NANOSECONDS;       // 纳秒。即千分之一微秒 
      
    • workQueue:阻塞队列BlockingQueue。用来存储等待执行的任务。一般有以下几种选择:

      • ArrayBlockingQueue:使用较少。是一个基于数组结构的有界阻塞队列。

      • LinkedBlockingQueue:使用较多。一个基于链表结构的阻塞队列。吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。

      • PriorityBlockingQueue:使用较少。一个具有优先级的无限阻塞队列。

      • SynchronousQueue:使用较多。一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

      • 线程池的排队策略(拒绝策略)与BlockingQueue有关。

    • threadFactory:线程工厂。用来创建线程。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

    • handler:拒绝策略。表示拒绝处理任务时的策略。四种取值:

      • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
        • 这是ThreadPoolExecutor线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,以便及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
      • ThreadPoolExecutor.DiscardPolicy:丢弃任务,不抛出异常。 如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
        • 使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,个人博客网站统计阅读量就可以采用的这种拒绝策略。
      • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务(重复此过程)
        • 此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
      • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。
        • 如果任务被拒绝了,则由调用线程(提交任务的线程,大多是main线程)直接执行此任务。
  • java线程池继承体系
    在这里插入图片描述

    • Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的。

    • ExecutorService接口声明了一些方法:submit()、invokeAll()、invokeAny()、shutDown()等。

    • 抽象类AbstractExecutorService基本实现了ExecutorService中声明的所有方法。

    • ThreadPoolExecutor中有几个非常重要的方法:execute()、submit()、shutdown()、shutdownNow()。

      • execute()是Executor接口中声明的方法,在ThreadPoolExecutor中进行了实现。这个方法是ThreadPoolExecutor的核心方法,通过该方法可以向线程池提交一个任务,交给线程池去处理。

      • submit()是ExecutorService中声明的方法,在AbstractExecutorService中就已经有了具体的实现,在ThreadPoolExecutor中并没有对其重写(直接通过父类继承过来)。这个方法也是提交任务交给线程池处理,区别就是参数传递的是Callable,能有返回值;而execute()的参数是Runnable,不能有返回值。submit()方法底层实际上还是调用了execute()方法,只不过它利用了 Future 来获取任务执行结果。

      • shutdown()shutdownNow()是用来关闭线程池的。

        • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
        • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
        • 我们可以通过调用线程池的 shutdownshutdownNow 方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
        • 只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow
      • ThreadPoolExecutor中还有一些方法:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,在此不重点讨论。

      • 另外还有两个不常用的方法,作用是动态调整线程池容量

        • setCorePoolSize:设置核心池大小
        • setMaximumPoolSize:设置线程池最大能创建的线程数目大小
        • 当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

1.2 线程池的实现原理

1.2.1 线程池的状态

在这里插入图片描述

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
  • runState 表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性;下面的几个 static final 变量表示 runState 可能的几个取值。
  • 当创建线程池后,初始时,线程池处于 RUNNING 状态;
  • RUNNING -> SHUTDOWN:如果调用了 shutdown()方法,则线程池处于 SHUTDOWN 状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕。
  • (RUNNING or SHUTDOWN) -> STOP:如果调用了 shutdownNow()方法,则线程池处于 STOP 状态,此时线程池不能接受新的任务,并且会去尝试立即终止正在执行的任务。
  • SHUTDOWN -> TIDYING:当线程池和队列都为空时,则线程池处于 TIDYING 状态。
  • STOP -> TIDYING:当线程池为空时,则线程池处于 TIDYING 状态。
  • TIDYING -> TERMINATED:当 terminated() 回调方法完成时,线程池处于 TERMINATED 状态。

1.2.2 execute()方法

  • 在ThreadPoolExecutor类中,最核心的方法是execute()方法(向线程池提交任务),虽然通过submit()也可以提交任务,但是实际上submit()方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可。

  • 我们可以使用 execute 提交任务,但是 execute 方法没有返回值,所以无法判断任务是否被线程池执行成功。所以一个方法假设操作10次数据库,那么我们必须将最重要的、必须更新成功的几次数据库操作搞成同步;而不需要返回值、相对来说不是特别重要、不一定要必须更新成功的数据库操作可以搞成异步。异步有两种,MQ和多线程,本文专指多线程。

    public void execute(Runnable command) {
    	if (command == null)
    		throw new NullPointerException();
    
    	int c = ctl.get();
    	if (workerCountOf(c) < corePoolSize) {
    		if (addWorker(command, true))
    			return;
    		c = ctl.get();
    	}
    	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);
    }
    
  • 我们也可以使用 submit 方法来提交任务,它会返回一个 Future ,那么我们可以通过这个 Future判断任务是否执行成功
    通过 Futureget 方法来获取返回值,get 方法会阻塞住主线程直到任务完成。而使用 get(long timeout, TimeUnit unit) 方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。

    Future<Object> future = executor.submit(harReturnValuetask);
    try {
         Object s = future.get();
    } catch (InterruptedException e) {
        // 处理中断异常
    } catch (ExecutionException e) {
        // 处理无法执行任务异常
    } finally {
        // 关闭线程池
        executor.shutdown();
    }
    
  • 线程池任务执行原理。(网图)
    在这里插入图片描述

1.3 线程池的分类

  • 可以直接使用new ThreadPoolExecutor创建线程池,可以使用Executors类中提供的几个静态方法来创建线程池(不推荐):

    Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量(工作线程大小)大小为Integer.MAX_VALUE
    Executors.newSingleThreadExecutor();    //创建容量为1的缓冲池
    Executors.newFixedThreadPool(int);      //创建固定容量大小的缓冲池
    
    • 从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

    • newFixedThreadPool创建的线程池corePoolSizemaximumPoolSize值是相等的,它使用的LinkedBlockingQueue

    • newSingleThreadExecutorcorePoolSizemaximumPoolSize都设置为1,也使用的LinkedBlockingQueue

    • newCachedThreadPoolcorePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

  • 详细描述如下。主要看下面的详细描述即可。

  • newCachedThreadPool

    • 概述:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    • 特点:

      • 工作线程大小为Interger.MAX_VALUE
      • 如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
    • 注意:在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

    • 实例

      public class CachedThreadPoolDemo {
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newCachedThreadPool();
              for (int i = 0; i < 10; i++) {
                  final int index = i;
                  try {
                      Thread.sleep(index * 1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  executorService.execute(() -> System.out.println(Thread.currentThread().getName() + " 执行,i = " + index));
              }
          }
      }
      
  • newFixedThreadPool

    • 概述:创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

    • 特点:FixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

    • 实例

      public class FixedThreadPoolDemo {
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newFixedThreadPool(3);
              for (int i = 0; i < 10; i++) {
                  final int index = i;
                  executorService.execute(() -> {
                      try {
                          System.out.println(Thread.currentThread().getName() + " 执行,i = " + index);
                          Thread.sleep(2000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  });
              }
          }
      }
      
  • newSingleThreadExecutor

    • 概述:创建一个单线程化的 Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。

    • 特点:单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

    • 实例:

      public class SingleThreadExecutorDemo {
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newSingleThreadExecutor();
              for (int i = 0; i < 10; i++) {
                  final int index = i;
                  executorService.execute(() -> {
                      try {
                          System.out.println(Thread.currentThread().getName() + " 执行,i = " + index);
                          Thread.sleep(2000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  });
              }
          }
      }
      
  • newScheduleThreadPool

    • 概述:创建一个线程池,可以安排任务在给定延迟后运行,或定期执行。

    • 实例

      public class ScheduledThreadPoolDemo {
      
          private static void delay() {
              ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
              scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + " 延迟 3 秒"), 3,
                      TimeUnit.SECONDS);
          }
      
          private static void cycle() {
              ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
              scheduledThreadPool.scheduleAtFixedRate(
                      () -> System.out.println(Thread.currentThread().getName() + " 延迟 1 秒,每 3 秒执行一次"), 1, 3,
                      TimeUnit.SECONDS);
          }
      
          public static void main(String[] args) {
              delay();
              cycle();
          }
      }
      
  • ThreadPoolTaskExecutor:Spring的线程池。

  • 我们一般是不允许使用Executors创建线程池,而是通过自定义ThreadPoolExecutor的方式。因为FixedThreadPool和SingleThreadExecutor的队列长度是Integer.MAX_VALUE,可能会堆积大量请求造成OOM。而CachedThreadPool和ScheduledThreadPool线程池的大小是Integer.MAX_VALUE,可能会创建大量的线程造成OOM。使用线程池,一般都会使用自定义参数的ThreadPoolExecutor

1.3 线程池要设置多大

  • 一般根据任务类型来配置线程池大小。
    • CPU密集型:CPU核数+1。1是为了防止出现意外或者错误导致一个线程不可用,有这个额外的线程。
    • IO密集型:2*CPU核数。
  • 通过Runtime.getRuntime().availableProcessors();获取CPU数目。
  • 《Java并发编程实战》和《Java虚拟机并发编程》两本书中都有介绍如何设置合理的线程池大小,我们依照 《Java并发编程实战》中的理论做分析和总结。
    • 使用多线程的目的是提高吞吐量(单位时间处理更多请求)、提高CPU利用率。
    • 《Java并发编程实战》中给出的计算密集型任务的线程池大小为:CPU核数+1。1是为了防止出现意外或者错误导致一个线程不可用,有这个额外的线程。
    • 《Java并发编程实战》中给出的IO密集型任务的线程池大小为:CPU数量*目标CPU使用率*(1+等待时间与计算时间的比值)
      • 其中目标CPU使用率大于等于0小于等于1。
      • 等待时间与计算时间的比值可以通过一些分析或监控工具来获得。
  • 通过参考Executors类中线程池的参数,我们一般将核心线程数和最大线程数设置为相等。

1.4 线程池使用实例

  • 企业级线程池类(可以直接放到项目中使用。机器参数后续更新本文):
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 如果队列满了, 就在原始线程执行, 不允许丢弃
     */
    @Slf4j
    public class CommonAsyncExecutor {
    
        private static ThreadPoolTaskExecutor executor;
    
        static  {
            executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(32);
            executor.setKeepAliveSeconds(128);
            executor.setQueueCapacity(128);
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
        }
    
        public static void execute(Runnable runnable) {
            log.info("CommonAsyncExecutor.execute {}", runnable);
            executor.submit(runnable);
        }
        
    }
    
  • 上述异步工具类使用方式:
    @Test
    public void test1000() throws Exception{
        CommonAsyncExecutor.execute(new Runnable() {
            @Override
            public void run() {
                method01();
                method02();
                method03();
            }
        });
    }
    
  • 线程池中的一些其他方法
    线程池中线程数目:executor.getPoolSize();
    队列中等待执行的任务数目:executor.getQueue().size();
    已执行完的任务数目:executor.getCompletedTaskCount(); 
    

1.5 Spring提供的线程池

  • 除了使用ThreadPoolExecutor,还可以使用Spring提供的线程池 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor。
@Configuration
public class TaskExecutorConfig implements AsyncConfigurer {

    // CPU核数 
    final static int nThreads = Runtime.getRuntime().availableProcessors();
    /**
     * Set the ThreadPoolExecutor's core pool size.
     */
    private static final int CORE_POOL_SIZE = nThreads;
    /**
     * Set the ThreadPoolExecutor's maximum pool size.
     */
    private static final int MAX_POOL_SIZE = nThreads;
    /**
     * Set the capacity for the ThreadPoolExecutor's BlockingQueue.
     */
    private static final int QUEUE_CAPACITY = 1000;

    /**
     * 通过重写getAsyncExecutor方法,制定默认的任务执行由该方法产生
     * <p>
     * 配置类实现AsyncConfigurer接口并重写getAsyncExcutor方法,并返回一个ThreadPoolTaskExevutor
     * 这样我们就获得了一个基于线程池的TaskExecutor
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.initialize();
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return taskExecutor;
    }
}

1.6 面试题思路:谈谈线程池

思路:为什么使用线程池 -> 线程池分类 -> 自定义线程池 -> 线程池各参数 -> 线程池执行原理。

  • 创建线程的方式有4种,使用线程池可以通过反复利用已经创建的线程降低线程频繁创建和销毁造成的消耗;并且当任务到达的时候,任务可以不用等到线程创建就立即执行,这就提高了程序的响应速度;另外使用线程池还能对线程进行统一的分配、监控,提高了线程的可管理性。
  • Executors线程工具类提供了很多类型的线程池供我们使用。比如CachedThreadPool,这是一个可缓存的线程池,如果线程长度超过处理需要,就会回收空闲线程,如果没有可以回收的线程,就会创建线程。还有ScheduledThreadPool,这个线程池支持定时或者周期性地执行任务。SingleThreadExecutor,这个线程池中只有一个工作线程,那么他就可以使任务按照顺序执行。还有一个FixedThreadPool。 这4种线程池实际上就是设置好参数的ThreadPoolExecutor。ThreadPoolExecutor是线程池中一个非常核心的实现类,使用线程池一般都会使用它。
  • 实际上,使用线程池一般是不会使用Executors工具类的,而是通过自定义ThreadPoolExecutor的方式。主要就是因为FixedThreadPool和SingleThreadExecutor的队列长度是Integer.MAX_VALUE,可能会堆积大量请求造成OOM,而CachedThreadPool和ScheduledThreadPool线程池的大小是Integer.MAX_VALUE,可能会创建大量的线程造成OOM。
  • ThreadPoolExecutor的有参构造器实际上只有一个。参数的话有核心线程数、最大线程数、keepAliveTime存活时间、阻塞队列、线程工厂。
  • 我们参考Executors线程工具类中参数的设置,一般会将核心线程数和最大线程数设置为相等,这个参数叫做线程池的大小。我们一般是根据任务的类型来配置线程池大小的,在《Java并发编程实战》这本书中就介绍过应该如何合理地设置线程池的大小。如果是CPU密集型,就设置为CPU合数+1,1是为了防止出现错误导致一个线程不能用,那么还有额外的线程保证任务继续执行。如果是IO密集型,那么线程池大小应该设置为 “CPU核数目标CPU使用率(1+等待时间与计算时间的比值)”。目标CPU利用率是大于等于0小于等于1的。等待时间与计算时间的比值是能通过一些分析或者监控工具来获得的。除了这种计算方式,还有一种凭经验得到的线程池大小,如果是IO密集型,那么线程池大小要设置为2倍的CPU核数。具体将线程池大小设置为什么,要在参考这两种算法的基础上进行实际测试,才能配出合适的线程池大小。
  • ThreadPoolExecutor中最重要的方法就是execute,这个方法实际上是在线程池的顶级接口Executor中定义的,能接受一个实现了Runnable接口的任务。任务提交之后,会先判断核心线程是否达到了最大核心线程数,如果没达到就创建核心线程执行任务,如果达到了就判断任务队列是否满了,如果没满就将任务放到任务队列中,如果满了就判断总线程数是否达到最大线程数,如果没达到就创建非核心线程执行任务,如果满了就执行拒绝策略。
  • 当QPS高峰过了之后,就没有那么多任务提交到线程池了。核心线程和非核心线程一直都是空跑,浪费资源,所以有一个参数叫做存活时间keepAliveTime,就是当多久没能从任务队列中获取任务时就销毁这个线程,默认这个参数只针对非核心线程有效。
  • 拒绝策略有四种,丢弃任务抛异常、丢弃任务不抛异常、丢弃队列最前面的任务然后重新提交被拒绝的任务、由调用线程处理该任务。我们一般就使用ThreadPoolExecutor线程池默认的AbortPolicy,就是丢弃并抛出RejectedExecutionException异常。比较适合一个关键的业务使用,能及时反馈程序的运行状态。 当然到底哪种拒绝策略最合适,还是要具体业务具体分析。
  • 阻塞队列一般使用基于基于链表的LinkedBlockingQueue。 队列大小设置多少要根据咱们的内存大小来具体情况具体分析。

1.7 参考

  • https://zhuanlan.zhihu.com/p/345976236
  • https://mp.weixin.qq.com/s/OKTW_mZnNJcRBrIFHONR3g
  • https://zhuanlan.zhihu.com/p/116426107
  • https://blog.csdn.net/u013066244/article/details/87898187
  • https://blog.csdn.net/suifeng629/article/details/98884972
  • https://www.cnblogs.com/dolphin0520/category/1426288.html
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值