【多线程】线程池

版权声明:版权所有,转载请标明链接。 https://blog.csdn.net/happyniceyq/article/details/83507848

1、好处

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

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

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

2、实现原理

当提交一个新任务到线程池时,线程池的处理流程:

1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作 线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这 个工作队列里。如果工作队列满了,则进入下个流程。

3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程 来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

ThreadPoolExecutor执行execute方法分下面4种情况。

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤 需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执 行这一步骤需要获取全局锁)。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法。

源码:

  public void execute(Runnable command) {
          if (command == null)
              throw new NullPointerException();
  // 如果线程数小于基本线程数,则创建线程并执行当前任务
  if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
      
  // 如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。
  if (runState == RUNNING && workQueue.offer(command)) {
          if (runState != RUNNING || poolSize == 0)
              ensureQueuedTaskHandled(command);
  }
  // 如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,
  // 则创建一个线程执行任务。
  else if (!addIfUnderMaximumPoolSize(command))
  // 抛出RejectedExecutionException异常
  reject(command); // is shutdown or saturated
              }
      }

3、ThreadPoolExecutor的构造方法

      public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                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;
      }

参数含义如下:

1、corePoolSize 核心线程池大小;2、maximumPoolSize 线程池最大容量大小;3、keepAliveTime 线程池空闲时,线程存活的时间;4、TimeUnit 时间单位;5、ThreadFactory 线程工厂;6、BlockingQueue 任务队列;7、RejectedExecutionHandler 线程拒绝策略;

4、任务队列

当我们提交一个新的任务到线程池,线程池会根据当前池中正在运行的线程数量来决定该任务的处理方式。处理方式有三种: 1、直接切换(SynchronusQueue)

2、无界队列(LinkedBlockingQueue)能够创建的最大线程数为corePoolSize,这时maximumPoolSize就不会起作用了。当线程池中所有的核心线程都是运行状态的时候,新的任务提交就会放入等待队列中。

3、有界队列(ArrayBlockingQueue)最大maximumPoolSize,能够降低资源消耗,但是这种方式使得线程池对线程调度变的更困难。因为线程池与队列容量都是有限的。所以想让线程池的吞吐率和处理任务达到一个合理的范围,又想使我们的线程调度相对简单,并且还尽可能降低资源的消耗,我们就需要合理的限制这两个数量

分配技巧: 如果想降低资源的消耗包括降低cpu使用率、操作系统资源的消耗、上下文切换的开销等等,可以设置一个较大的队列容量和较小的线程池容量,这样会降低线程池的吞吐量。如果我们提交的任务经常发生阻塞,我们可以调整maximumPoolSize。如果我们的队列容量较小,我们需要把线程池大小设置的大一些,这样cpu的使用率相对来说会高一些。但是如果线程池的容量设置的过大,提高任务的数量过多的时候,并发量会增加,那么线程之间的调度就是一个需要考虑的问题。这样反而可能会降低处理任务的吞吐量。

5、RejectedExecutionHandler 线程拒绝策略:

  • AbortPolicy:直接抛出异常。该策略是线程池的默认策略。使用该策略时,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。

  • DiscardPolicy:不处理,丢弃掉。 这个策略和AbortPolicy的slient版本,如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。

  • DiscardOldestPolicy:丢弃队列里最近的一个任务, 并执行任务。 丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队。

  • CallerRunsPolicy:只用调用者所在线程来运行任务。 如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。

  • 自定义:实现RejectedExecutionHandler接口,并且实现rejectedExecution方法就可以了

6、种类

newCachedThreadPool:

      public static ExecutorService newCachedThreadPool() {
          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                        60L, TimeUnit.SECONDS,
                                        new SynchronousQueue<Runnable>());
      }
  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)

  • 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。

  • 适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool:

      public static ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
      }
  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

  • 通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)

  • 适用:执行长期的任务,性能好很多

newSingleThreadExecutor:

      public static ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列

  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)

  • 适用:一个任务一个任务执行的场景

NewScheduledThreadPool:

  //源码:
  public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
      return new ScheduledThreadPoolExecutor(corePoolSize);
  }
  ​
  public ScheduledThreadPoolExecutor(int corePoolSize) {
      super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,//此处super指的是ThreadPoolExecutor
            new DelayedWorkQueue());
  }
  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列

  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构

  • 适用:周期性执行任务的场景

7、实现项目应用

基础数据Excel导入多表操作;

场景说明:

Excel导入基础数据,得到的导入数据list需要插入到A表、B表。由于基础数据动辄上万条数据,如果只是使用单线程进行导入,时间周期长,系统性能会大大降低。解决方法:使用使用自定义线程池,设置了多线程,每个线程对list进行分组,分组后依次插入到数据库A、B表中。

猜你喜欢

转载自blog.csdn.net/happyniceyq/article/details/83507848