线程池ThreadPoolExecutor

为什么使用线程池,线程池有什么优势?

因为线程的创建和销毁都会伴随着系统的开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率。而且线程并发数量过多,抢占系统资源从而导致阻塞。所以需要对线程进行简单的管理,比如:延时执行,定时循环执行,这就需要线程池来进行管理。

线程池有以下好处:

  • 1、降低资源消耗;
  • 2、提高响应速度;
  • 3、提高线程的可管理性。

Java1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。

线程池的实现原理

当向线程池提交一个任务时,线程池会按照以下的流程执行:

  • 1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  • 2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。、
  • 3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

 

 

线程池ThreadPoolExecutor_第1张图片

线程池ThreadPoolExecutor_第2张图片

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

  • 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务
  • 2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
  • 3)如果无法将任 务加入BlockingQueue(队列已满),则创建新的线程来处理任务
  • 4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用jectedExecutionHandler中的四种饱和策略。

 

线程池的饱和策略

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认情况下的策略为AbortPolicy。

可以通过setRejectedExecutionHandler来修改。线程池框架提供了4中饱和策略。

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:会将任务回退到调用execute的主线程中执行。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
     

线程池的创建

可以通过ThreadPoolExecutor来创建一个线程池,并且这个框架提供了四种不同类型的线程池,分别是:

①FixedThreadPool():

固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。

适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景。

②CachedThreadPool():

可以缓存的线程池默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列,和FixedThreadPool创建的线程池不同,CachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。

适用于执行很多的短期异步任务的小程序。

③SingleThreadExecutor();

单线程线程池按顺序来执行线程任务里面的核心线程数和线程数都是1,内部使用LinkedBlockingQueue作为阻塞队列。每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。

适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

④ScheduledThreadPool():

初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。

适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的应用场景

 

ThreadPoolExecutor构造函数

这是四种线程池类型都是通过ThreadPoolExecutor框架来创建的,所以有必要了解一下ThreadPoolExecutor的构造函数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler) {
        
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
}

创建一个线程池需要多个参数,分别是:

①int corePoolSize 

该线程池中核心线程数最大值,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

②int maximumPoolSize

该线程池中线程总数最大值,如果当前阻塞队列满了,且继续提交任务,并且当前线程数小于maximumPoolSize,则创建新的线程执行任务。

③long keepAliveTime

该线程池中非核心线程闲置超时时长,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

④TimeUnit unit

keepAliveTime的单位,TimeUnit是一个枚举类型。单位可以选择:天、小时、分钟、毫秒、微妙、纳秒等。

⑤BlockingQueue workQueue

用于保存等待执行的任务的阻塞队列。可以一下选择:

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

 

⑥RejectedExecutionHandler
饱和策略,共有四种饱和策略,上面已经提到过了,也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。

 

向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()submit()方法。
①execute()

用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功

②submit()

用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线
程一段时间后立即返回,这时候有可能任务没有执行完。

线程池的关闭

可以通过调用线程池的shutdown()shutdownNow()方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别:

  • shutdownNow:首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
  • shutdown:只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

当所有的任务都已关闭后,才表示线程池关闭成功,至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法
 

线程池的监控

如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性:

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减
  • getActiveCount:获取活动的线程数。

 

 

 

 

你可能感兴趣的:(线程池,线程池四大参数,并发编程与多线程)